Browse Source

Implement shared HEIF color conversion

pull/2633/head
James Jackson-South 2 months ago
parent
commit
7b1bb3e8ed
  1. 20
      HEIF_IMPLEMENTATION_PLAN.md
  2. 187
      src/ImageSharp/Formats/Heif/Av1/Av1ColorConversionParameters.cs
  3. 36
      src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.RowConverters.cs
  4. 349
      src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs
  5. 498
      src/ImageSharp/Formats/Heif/Color/HeifColorConversionParameters.cs
  6. 22
      src/ImageSharp/Formats/Heif/Color/HeifColorConverter.CoefficientOperator.cs
  7. 102
      src/ImageSharp/Formats/Heif/Color/HeifColorConverter.ConstantLuminanceOperator.cs
  8. 136
      src/ImageSharp/Formats/Heif/Color/HeifColorConverter.ICtCpOperator.cs
  9. 22
      src/ImageSharp/Formats/Heif/Color/HeifColorConverter.IdentityOperator.cs
  10. 118
      src/ImageSharp/Formats/Heif/Color/HeifColorConverter.IptC2Operator.cs
  11. 44
      src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Operator.cs
  12. 192
      src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Samples.cs
  13. 22
      src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Smpte2085Operator.cs
  14. 22
      src/ImageSharp/Formats/Heif/Color/HeifColorConverter.YCgCoOperator.cs
  15. 42
      src/ImageSharp/Formats/Heif/Color/HeifColorConverter.YCgCoReversibleOperator.cs
  16. 36
      src/ImageSharp/Formats/Heif/Color/HeifColorConverter.cs
  17. 92
      src/ImageSharp/Formats/Heif/Color/HeifTransferFunctions.Simd.cs
  18. 76
      src/ImageSharp/Formats/Heif/Color/HeifTransferFunctions.cs
  19. 40
      src/ImageSharp/Formats/Heif/Hevc/HevcChromaSampleLocation.cs
  20. 41
      src/ImageSharp/Formats/Heif/Hevc/HevcPictureBuffer.cs
  21. 8
      src/ImageSharp/Formats/Heif/Hevc/HevcVideoUsabilityInformation.cs
  22. 592
      src/ImageSharp/Formats/Heif/Hevc/HevcYuvConverter.cs
  23. 185
      tests/ImageSharp.Benchmarks/Codecs/Heif/HevcColorConversionBenchmarks.cs
  24. 74
      tests/ImageSharp.Tests/Formats/Heif/Color/HeifTransferFunctionsTests.cs
  25. 303
      tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcYuvConverterTests.cs

20
HEIF_IMPLEMENTATION_PLAN.md

@ -40,6 +40,8 @@ Checkboxes may be marked complete only when the implementation and the verificat
- [x] Implement the H.273 V4 IPT-C2 and reversible YCgCo-Re/YCgCo-Ro transforms in both directions as closed SIMD operators, including their reduced RGB precision, signed integer lifting, full- and limited-range behavior, and 4:4:4 constraint.
- [x] Verify IPT-C2 against independently calculated matrix values and verify YCgCo-Re/YCgCo-Ro against exact 8/10/12-bit code values, limited-range black/white values, and SIMD-lane/scalar-tail parity.
- [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.
- [x] Move the H.273 parameter resolver, closed color operators, transfer functions, sample loading/storage, chroma filtering, and RGB packing into one shared HEIF color pipeline used by both AV1 and HEVC rather than maintaining codec-specific arithmetic copies.
- [x] Implement sequential pooled HEVC conversion in both directions for monochrome, 4:2:0, 4:2:2, and 4:4:4; independent luma/chroma precision; 8/10/12-bit full/limited ranges; and all six progressive 4:2:0 chroma sample locations. Eight-bit paths use JPEG's optimized RGB plane contracts, while high-bit-depth paths retain 16-bit packed pixels.
- [ ] 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.
@ -48,6 +50,7 @@ Checkboxes may be marked complete only when the implementation and the verificat
- [ ] 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.
- [x] Add permanent frame-wide `HevcColorConversionBenchmarks` coverage and record the .NET 10 short-run plane-conversion baseline on 2026-08-26. SIMD decode measured 1.141/2.640/2.668 ms and encode measured 2.089/2.481/2.412 ms at 8/10/12 bits with 504-712 B allocated per frame. Forced-scalar decode measured 13.42/28.35/27.60 ms and encode measured 14.82/14.67/14.45 ms, making the normal SIMD pipeline 10.3-11.8 times faster for decode and 5.9-7.1 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.
@ -201,8 +204,8 @@ This snapshot pins or classifies the available references and failures; it does
| Managed implementation | Normative behavior | Reviewed implementation reference | Use |
| --- | --- | --- | --- |
| `Av1YuvConverter.ConvertToRgb`, `ConvertFromRgb`, scalar row conversion, chroma reconstruction, and chroma downsampling | ITU-T H.273 (V4) equations 14-16 and 27-84, including limited/full-range scaling, chromaticity-derived equations 39-47, YCgCo equations 51-57, constant-luminance equations 66-75, IPT-C2, YCgCo-Re/YCgCo-Ro, and the PQ/HLG ICtCp matrices; AV1 section 6.4.2 chroma sample positions | Official ITU-T H.273 (V4) (07/2024); libavif `src/reformat.c` and `src/colr.c` at `092276ce89098ead06db80975173191e5fee1826`; libaom `aom/aom_image.h` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Scalar behavioral oracle for full/limited-range conversion at 8, 10, and 12 bits. Decode and encode cover every H.273 V4 matrix code point through 17: identity, YCgCo, coefficient-based NCL, both fixed and chromaticity-derived constant/non-constant-luminance systems, SMPTE ST 2085, ICtCp, IPT-C2, YCgCo-Re, and YCgCo-Ro. The common paths cover monochrome, YUV 4:2:0, 4:2:2, and 4:4:4 with AV1 chroma sample positioning; identity and the reversible lifting transforms enforce their full-chroma constraint. Limited-range YCgCo retains the 219-code scale inherited from its R/G/B inputs instead of applying YCbCr's unrelated 224-code chroma range. YCgCo-Re and YCgCo-Ro apply range adjustment at their reduced-precision RGB code values before signed integer lifting. Chromaticity derivation uses every defined H.273 primary and matches libavif's BT.709 fallback for unspecified or reserved primaries. The ICtCp and IPT-C2 inverses are derived from their exact H.273 matrices rather than unrelated display conversions. The closed operators provide `Vector512`, `Vector256`, and `Vector128` implementations with one scalar tail. |
| `Av1TransferFunctions` | ITU-T H.273 (V4) Table 3 transfer characteristics 1-18 | Official ITU-T H.273 (V4) (07/2024); libavif `src/colr.c` at `092276ce89098ead06db80975173191e5fee1826` | Apply every AV1-signallable transfer function required by constant-luminance and ICtCp color conversion. Retain the H.273 normalized PQ and HLG definitions; do not import libavif's display-oriented 203-nit scaling or HLG OOTF into codec sample interpretation. Use libavif's midpoint convention only for the non-bijective zero code of the two logarithmic curves. |
| `HeifColorConversionParameters`, `HeifColorConverter`, `Av1YuvConverter`, and `HevcYuvConverter` | ITU-T H.273 (V4) equations 14-16 and 27-84, including limited/full-range scaling, chromaticity-derived equations 39-47, YCgCo equations 51-57, constant-luminance equations 66-75, IPT-C2, YCgCo-Re/YCgCo-Ro, and the PQ/HLG ICtCp matrices; AV1 section 6.4.2 and HEVC Annex E chroma sample positions | Official ITU-T H.273 (V4) (07/2024); libavif `src/reformat.c` and `src/colr.c` at `092276ce89098ead06db80975173191e5fee1826`; libaom `aom/aom_image.h` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Scalar behavioral oracle for full/limited-range conversion at 8, 10, and 12 bits. Decode and encode cover every H.273 V4 matrix code point through 17: identity, YCgCo, coefficient-based NCL, both fixed and chromaticity-derived constant/non-constant-luminance systems, SMPTE ST 2085, ICtCp, IPT-C2, YCgCo-Re, and YCgCo-Ro. Shared paths cover monochrome, YUV 4:2:0, 4:2:2, and 4:4:4; AV1 centered/co-sited placement; and all six HEVC progressive 4:2:0 sample locations. Identity and reversible lifting enforce their full-chroma constraint. Limited-range YCgCo retains the 219-code scale inherited from its R/G/B inputs instead of applying YCbCr's unrelated 224-code chroma range. YCgCo-Re and YCgCo-Ro apply range adjustment at reduced-precision RGB code values before signed integer lifting. Chromaticity derivation uses every defined H.273 primary and matches libavif's BT.709 fallback for unspecified or reserved primaries. ICtCp and IPT-C2 inverses come from the exact H.273 matrices rather than unrelated display conversions. Closed operators provide `Vector512`, `Vector256`, and `Vector128` implementations with one scalar tail. Eight-bit codec wrappers use JPEG's RGB plane pack/unpack contracts; high-bit-depth wrappers retain native 16-bit packed pixels. |
| `HeifTransferFunctions` | ITU-T H.273 (V4) Table 3 transfer characteristics 1-18 | Official ITU-T H.273 (V4) (07/2024); libavif `src/colr.c` at `092276ce89098ead06db80975173191e5fee1826` | Apply every HEVC/AV1-signallable transfer function required by constant-luminance, ICtCp, and IPT-C2 conversion. Retain the H.273 normalized PQ and HLG definitions; do not import libavif's display-oriented 203-nit scaling or HLG OOTF into codec sample interpretation. Use libavif's midpoint convention only for the non-bijective zero code of the two logarithmic curves. |
| `ObuReader.ReadSequenceHeader`, `ReadUncompressedFrameHeader`, decoder-model parsing, and operating-parameter consumption | AV1 sections 5.5.2 through 5.5.4 sequence timing and decoder-model syntax, section 5.9.2 uncompressed frame-header syntax, and section 5.9.31 temporal-point syntax | libaom `av1/decoder/decodeframe.c` functions `av1_read_decoder_model_info`, `av1_read_op_parameters_info`, `read_temporal_point_info`, and `read_uncompressed_header`, plus `common/av1_config.c`, at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Read the normative 32-bit decoding-tick field; consume operating-point buffer delays and the layer-applicable frame removal times needed to keep a non-reduced still-image sequence aligned; and read presentation time only under the normative decoder-model condition. Retain no scheduling or playback behavior from those values and introduce no ISO BMFF timing, track, sample-table, or sequence surface. |
| `Av1FrameBuffer` high-bit-depth sample layout and `Av1YuvConverter` 10/12-bit packed-pixel conversion | AV1 section 6.4.1 bit depth and H.273 sample-range scaling | libaom `aom_scale/yv12config.h`, `av1/common/idct.c`, and `av1/common/reconintra.c` at `03087864cf4bea6abb0d28f95cf7843511413d8f`; libavif `src/avif.c` and `src/reformat.c` at `092276ce89098ead06db80975173191e5fee1826` | Establish two-byte native sample storage with sample-unit strides for 10/12-bit reconstruction and use ImageSharp's existing `Rgb48` pixel-operation paths in both directions so packed-pixel staging does not reduce high-bit-depth samples to eight bits. |
| `Av1PredictionDecoder`, `Av1HighBitDepthPredictor`, `Av1ChromaFromLumaContext`, `Av1PartitionInfo`, and the scalar DC, directional, Paeth, smooth, filter-intra, and chroma-from-luma predictors | AV1 sections 7.11.2 and 7.11.2.3 intra prediction | libaom `aom_dsp/intrapred.c`, `av1/common/reconintra.c`, `av1/common/av1_common_int.h`, `av1/common/blockd.h`, `av1/common/cfl.c`, and `av1/common/cfl.h` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Behavioral oracle for luma/chroma mode-neighbor addressing, directional upsampling, Paeth selection, smooth normalization, filter-intra taps, high-bit-depth clipping, chroma-from-luma storage/subsampling, and chroma-from-luma row strides. Existing managed scalar tables and predictors remain the implementation base. The WIP rectangular byte-pipeline smooth digest expectations encode width/height-swapped weights and must be replaced only from an independently generated oracle, not regenerated from this implementation. |
@ -472,16 +475,19 @@ 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.
- [x] Implement pooled HEVC plane conversion for monochrome, 4:2:0, 4:2:2, and 4:4:4, including exact encode-coordinate and decode-interpolation coverage for all six progressive 4:2:0 sample locations.
- [ ] Connect reconstructed HEVC pictures and complete the independently verified 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.
- [x] Implement the equivalent HEVC plane paths with independent luma/chroma precision, native 16-bit high-depth staging, and exact limited-range endpoint coverage.
- [ ] Independently verify the complete HEVC range/precision/plane-layout cross-product after reconstruction is connected.
- [ ] 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.
- [x] Implement one shared H.273 matrix and transfer-function operator set in both directions for AV1 and HEVC, including identity, derived coefficients, constant luminance, SMPTE ST 2085, PQ/HLG ICtCp, IPT-C2, YCgCo-Re, and YCgCo-Ro.
- [ ] Add independent conformance vectors for signaling combinations that libavif deliberately rejects, then verify the operator set through completed HEVC reconstruction.
- [ ] 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.
- [x] HEVC plane conversion uses the same shared static operators, descending SIMD-width traversal, JPEG byte-plane pack/unpack, pooled high-depth staging, and scalar fallback; full-HD SIMD/scalar timings are recorded above.
- [ ] Connect the HEVC reconstruction path and record complete HEIC item and sequence measurements before closing this item.
- [ ] 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.

187
src/ImageSharp/Formats/Heif/Av1/Av1ColorConversionParameters.cs

@ -1,187 +0,0 @@
// 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>
/// <param name="encodedSampleMaximum">The largest encoded sample value.</param>
/// <param name="rgbBias">The reversible transform's RGB code-value bias.</param>
/// <param name="rgbScale">The reversible transform's RGB code-value range.</param>
/// <param name="rgbSampleMaximum">The largest reversible transform RGB code value.</param>
public Av1ColorConversionParameters(
float kr,
float kg,
float kb,
ObuTransferCharacteristics transferCharacteristics,
in Av1ConstantLuminanceScales constantLuminanceScales,
float lumaBias,
float lumaScale,
float chromaBias,
float chromaScale,
float encodedSampleMaximum,
float rgbBias,
float rgbScale,
float rgbSampleMaximum)
{
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;
this.EncodedSampleMaximum = encodedSampleMaximum;
this.RgbBias = rgbBias;
this.RgbScale = rgbScale;
this.RgbSampleMaximum = rgbSampleMaximum;
}
/// <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>
/// Gets the largest encoded sample value.
/// </summary>
public float EncodedSampleMaximum { get; }
/// <summary>
/// Gets the reversible transform's RGB code-value bias.
/// </summary>
public float RgbBias { get; }
/// <summary>
/// Gets the reversible transform's RGB code-value range.
/// </summary>
public float RgbScale { get; }
/// <summary>
/// Gets the largest reversible transform RGB code value.
/// </summary>
public float RgbSampleMaximum { 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; }
}

36
src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.RowConverters.cs

@ -5,8 +5,10 @@ using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Color;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using static SixLabors.ImageSharp.Formats.Heif.Color.HeifColorConverterBase;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
@ -24,7 +26,7 @@ internal static partial class Av1YuvConverter
private readonly struct YuvToRgbRowConverter<TPixel, TSample, TLoader>
where TPixel : unmanaged, IPixel<TPixel>
where TSample : unmanaged
where TLoader : struct, ISampleLoader<TSample>
where TLoader : struct, IHeifSampleLoader<TSample>
{
/// <summary>
/// The configuration used by bulk pixel conversion.
@ -59,7 +61,7 @@ internal static partial class Av1YuvConverter
/// <summary>
/// The frame-scoped color-model converter.
/// </summary>
private readonly Av1ColorConverterBase colorConverter;
private readonly HeifColorConverterBase colorConverter;
/// <summary>
/// The signaled chroma sample position used for reconstruction.
@ -92,7 +94,7 @@ internal static partial class Av1YuvConverter
Configuration configuration,
Av1FrameBuffer<byte> frameBuffer,
ImageFrame<TPixel> image,
Av1ColorConverterBase colorConverter)
HeifColorConverterBase colorConverter)
{
this.configuration = configuration;
this.frameBuffer = frameBuffer;
@ -229,7 +231,7 @@ internal static partial class Av1YuvConverter
private readonly struct RgbToYuvRowConverter<TPixel, TSample, TStorer>
where TPixel : unmanaged, IPixel<TPixel>
where TSample : unmanaged
where TStorer : struct, ISampleStorer<TSample>
where TStorer : struct, IHeifSampleStorer<TSample>
{
/// <summary>
/// The configuration used by bulk pixel conversion.
@ -264,7 +266,7 @@ internal static partial class Av1YuvConverter
/// <summary>
/// The frame-scoped color-model converter.
/// </summary>
private readonly Av1ColorConverterBase colorConverter;
private readonly HeifColorConverterBase colorConverter;
/// <summary>
/// The largest value represented by the encoded AV1 bit depth.
@ -298,7 +300,7 @@ internal static partial class Av1YuvConverter
Configuration configuration,
Av1FrameBuffer<byte> frameBuffer,
ImageFrame<TPixel> image,
Av1ColorConverterBase colorConverter,
HeifColorConverterBase colorConverter,
float sampleMaximum)
{
this.configuration = configuration;
@ -383,8 +385,26 @@ internal static partial class Av1YuvConverter
}
else
{
WriteSubsampledSamples<TSample, TStorer>(cbRow0, cbRow1, uDestination, chromaScale, chromaBias, this.sampleMaximum);
WriteSubsampledSamples<TSample, TStorer>(crRow0, crRow1, vDestination, chromaScale, chromaBias, this.sampleMaximum);
float secondRowWeight = hasSecondSourceRow ? 0.5F : 0F;
WriteSubsampledSamples<TSample, TStorer>(
cbRow0,
cbRow1,
uDestination,
true,
secondRowWeight,
chromaScale,
chromaBias,
this.sampleMaximum);
WriteSubsampledSamples<TSample, TStorer>(
crRow0,
crRow1,
vDestination,
true,
secondRowWeight,
chromaScale,
chromaBias,
this.sampleMaximum);
}
}

349
src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs

@ -2,12 +2,13 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Color;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.PixelFormats;
using static SixLabors.ImageSharp.Formats.Heif.Color.HeifColorConverterBase;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
@ -36,12 +37,12 @@ internal static partial class Av1YuvConverter
public static void ConvertToRgb<TPixel>(Configuration configuration, Av1FrameBuffer<byte> frameBuffer, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Av1ColorConversionParameters parameters = GetConversionParameters(frameBuffer, out Av1ColorConversionMode mode);
HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer, out HeifColorConversionMode mode);
Av1ColorConverterBase colorConverter = Av1ColorConverterBase.Create(mode, in parameters, frameBuffer.ColorFormat == Av1ColorFormat.Yuv400);
HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, frameBuffer.ColorFormat == Av1ColorFormat.Yuv400);
if (frameBuffer.BitDepth == Av1BitDepth.EightBit)
{
YuvToRgbRowConverter<TPixel, byte, ByteSampleLoader> converter = new(configuration, frameBuffer, image, colorConverter);
YuvToRgbRowConverter<TPixel, byte, HeifByteSampleLoader> converter = new(configuration, frameBuffer, image, colorConverter);
using IMemoryOwner<float> scratchOwner = configuration.MemoryAllocator.Allocate<float>(converter.BufferLength);
using IMemoryOwner<TPixel> proxyOwner = configuration.MemoryAllocator.Allocate<TPixel>(image.Width + 3);
Span<float> scratch = scratchOwner.GetSpan();
@ -53,7 +54,7 @@ internal static partial class Av1YuvConverter
}
else
{
YuvToRgbRowConverter<TPixel, ushort, UShortSampleLoader> converter = new(configuration, frameBuffer, image, colorConverter);
YuvToRgbRowConverter<TPixel, ushort, HeifUShortSampleLoader> converter = new(configuration, frameBuffer, image, colorConverter);
using IMemoryOwner<float> owner = configuration.MemoryAllocator.Allocate<float>(converter.BufferLength);
Span<float> scratch = owner.GetSpan();
for (int y = 0; y < image.Height; y++)
@ -73,16 +74,16 @@ internal static partial class Av1YuvConverter
public static void ConvertFromRgb<TPixel>(Configuration configuration, ImageFrame<TPixel> image, Av1FrameBuffer<byte> frameBuffer)
where TPixel : unmanaged, IPixel<TPixel>
{
Av1ColorConversionParameters parameters = GetConversionParameters(frameBuffer, out Av1ColorConversionMode mode);
float sampleMaximum = parameters.EncodedSampleMaximum;
HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer, out HeifColorConversionMode mode);
float sampleMaximum = parameters.LumaSampleMaximum;
bool isMonochrome = frameBuffer.ColorFormat == Av1ColorFormat.Yuv400;
Av1ColorConverterBase colorConverter = Av1ColorConverterBase.Create(mode, in parameters, isMonochrome);
HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, isMonochrome);
int rowShift = !isMonochrome && frameBuffer.ColorConfig.SubSamplingY ? 1 : 0;
int iterationCount = (image.Height + rowShift) >> rowShift;
if (frameBuffer.BitDepth == Av1BitDepth.EightBit)
{
RgbToYuvRowConverter<TPixel, byte, ByteSampleStorer> converter = new(configuration, frameBuffer, image, colorConverter, sampleMaximum);
RgbToYuvRowConverter<TPixel, byte, HeifByteSampleStorer> converter = new(configuration, frameBuffer, image, colorConverter, sampleMaximum);
using IMemoryOwner<float> componentOwner = configuration.MemoryAllocator.Allocate<float>(converter.ComponentBufferLength);
Span<float> components = componentOwner.GetSpan();
for (int y = 0; y < iterationCount; y++)
@ -92,7 +93,7 @@ internal static partial class Av1YuvConverter
}
else
{
RgbToYuvRowConverter<TPixel, ushort, UShortSampleStorer> converter = new(configuration, frameBuffer, image, colorConverter, sampleMaximum);
RgbToYuvRowConverter<TPixel, ushort, HeifUShortSampleStorer> converter = new(configuration, frameBuffer, image, colorConverter, sampleMaximum);
using IMemoryOwner<Rgb48> packedOwner = configuration.MemoryAllocator.Allocate<Rgb48>(image.Width);
using IMemoryOwner<float> componentOwner = configuration.MemoryAllocator.Allocate<float>(converter.ComponentBufferLength);
Span<Rgb48> packed = packedOwner.GetSpan()[..image.Width];
@ -110,288 +111,25 @@ internal static partial class Av1YuvConverter
/// <param name="frameBuffer">The AV1 frame containing the signaled color configuration.</param>
/// <param name="mode">The resolved conversion mode.</param>
/// <returns>The resolved conversion parameters.</returns>
private static Av1ColorConversionParameters GetConversionParameters(Av1FrameBuffer<byte> frameBuffer, out Av1ColorConversionMode mode)
private static HeifColorConversionParameters GetConversionParameters(Av1FrameBuffer<byte> frameBuffer, out HeifColorConversionMode mode)
{
mode = Av1ColorConversionMode.Coefficients;
float kr = 0F;
float kb = 0F;
// These code points and fixed coefficient matrices are defined by H.273.
switch (frameBuffer.ColorConfig.MatrixCoefficients)
{
case ObuMatrixCoefficients.Identity:
mode = Av1ColorConversionMode.Identity;
break;
case ObuMatrixCoefficients.Bt709:
kr = 0.2126F;
kb = 0.0722F;
break;
case ObuMatrixCoefficients.Fcc:
kr = 0.30F;
kb = 0.11F;
break;
case ObuMatrixCoefficients.Bt470BG:
case ObuMatrixCoefficients.Bt601:
case ObuMatrixCoefficients.Unspecified:
// libavif falls back to BT.601 coefficients when the matrix is unavailable.
kr = 0.299F;
kb = 0.114F;
break;
case ObuMatrixCoefficients.Smpte240:
kr = 0.212F;
kb = 0.087F;
break;
case ObuMatrixCoefficients.SmpteYCgCo:
mode = Av1ColorConversionMode.YCgCo;
break;
case ObuMatrixCoefficients.Bt2020NonConstantLuminance:
kr = 0.2627F;
kb = 0.0593F;
break;
case ObuMatrixCoefficients.Bt2020ConstantLuminance:
mode = Av1ColorConversionMode.ConstantLuminance;
kr = 0.2627F;
kb = 0.0593F;
break;
case ObuMatrixCoefficients.Smpte2085:
mode = Av1ColorConversionMode.Smpte2085;
break;
case ObuMatrixCoefficients.ChromaticityDerivedNonConstantLuminance:
GetChromaticityDerivedCoefficients(frameBuffer.ColorConfig.ColorPrimaries, out kr, out kb);
break;
case ObuMatrixCoefficients.ChromaticityDerivedConstantLuminance:
mode = Av1ColorConversionMode.ConstantLuminance;
GetChromaticityDerivedCoefficients(frameBuffer.ColorConfig.ColorPrimaries, out kr, out kb);
break;
case ObuMatrixCoefficients.Bt2100ICtCp:
mode = Av1ColorConversionMode.ICtCp;
break;
case ObuMatrixCoefficients.IptC2:
mode = Av1ColorConversionMode.IptC2;
break;
case ObuMatrixCoefficients.YCgCoRe:
case ObuMatrixCoefficients.YCgCoRo:
mode = Av1ColorConversionMode.YCgCoReversible;
break;
default:
throw new NotSupportedException($"AV1 matrix coefficients '{frameBuffer.ColorConfig.MatrixCoefficients}' are not currently supported.");
}
float kg = 1F - kr - kb;
bool isMonochrome = frameBuffer.ColorFormat == Av1ColorFormat.Yuv400;
bool isFullRange = frameBuffer.ColorConfig.ColorRange;
bool requiresFullChroma = mode is Av1ColorConversionMode.Identity or Av1ColorConversionMode.YCgCoReversible;
if (requiresFullChroma && !isMonochrome && frameBuffer.ColorFormat != Av1ColorFormat.Yuv444)
{
throw new InvalidImageContentException($"AV1 {frameBuffer.ColorConfig.MatrixCoefficients} matrix coefficients require YUV 4:4:4 sampling.");
}
if (frameBuffer.ColorConfig.ChromaSamplePosition == ObuChromoSamplePosition.Reserved)
{
throw new InvalidImageContentException("The reserved AV1 chroma sample position is invalid.");
}
int bitCount = frameBuffer.BitDepth.GetBitCount();
int depthScale = 1 << (bitCount - 8);
float sampleMaximum = (1 << bitCount) - 1;
float chromaBias = 128F * depthScale;
float lumaBias = isFullRange ? 0F : 16F * depthScale;
float lumaScale = isFullRange ? sampleMaximum : 219F * depthScale;
// H.273 limited-range YCgCo first maps R, G, and B through the 219-code luma range, so its
// difference components inherit that scale instead of the 224-code scale used by YCbCr.
float chromaScale = isFullRange || mode == Av1ColorConversionMode.YCgCo
? lumaScale
: 224F * depthScale;
float rgbBias = 0F;
float rgbScale = 1F;
float rgbSampleMaximum = 1F;
if (mode == Av1ColorConversionMode.YCgCoReversible)
{
int bitOffset = frameBuffer.ColorConfig.MatrixCoefficients == ObuMatrixCoefficients.YCgCoRe ? 2 : 1;
int rgbBitCount = bitCount - bitOffset;
float rgbDepthScale = MathF.ScaleB(1F, rgbBitCount - 8);
rgbSampleMaximum = (1 << rgbBitCount) - 1;
rgbBias = isFullRange ? 0F : 16F * rgbDepthScale;
rgbScale = isFullRange ? rgbSampleMaximum : 219F * rgbDepthScale;
// The reversible lifting transform operates on raw integer code values. Range adjustment therefore
// belongs inside the operator, while the row converter only normalizes the encoded Y, Cg, and Co planes.
lumaBias = 0F;
lumaScale = sampleMaximum;
chromaScale = sampleMaximum;
}
ObuTransferCharacteristics transferCharacteristics = frameBuffer.ColorConfig.TransferCharacteristics;
Av1ConstantLuminanceScales constantLuminanceScales = mode == Av1ColorConversionMode.ConstantLuminance
? new Av1ConstantLuminanceScales(transferCharacteristics, kr, kb)
: default;
return new Av1ColorConversionParameters(
kr,
kg,
kb,
transferCharacteristics,
in constantLuminanceScales,
lumaBias,
lumaScale,
chromaBias,
chromaScale,
sampleMaximum,
rgbBias,
rgbScale,
rgbSampleMaximum);
}
/// <summary>
/// Computes the luma coefficients defined by the signaled H.273 primary chromaticities.
/// </summary>
/// <param name="colorPrimaries">The signaled color-primary code point.</param>
/// <param name="kr">The resulting red luma coefficient.</param>
/// <param name="kb">The resulting blue luma coefficient.</param>
private static void GetChromaticityDerivedCoefficients(
ObuColorPrimaries colorPrimaries,
out float kr,
out float kb)
{
float redX;
float redY;
float greenX;
float greenY;
float blueX;
float blueY;
float whiteX;
float whiteY;
switch (colorPrimaries)
{
case ObuColorPrimaries.Bt470M:
redX = 0.67F;
redY = 0.33F;
greenX = 0.21F;
greenY = 0.71F;
blueX = 0.14F;
blueY = 0.08F;
whiteX = 0.310F;
whiteY = 0.316F;
break;
case ObuColorPrimaries.Bt470BG:
redX = 0.64F;
redY = 0.33F;
greenX = 0.29F;
greenY = 0.60F;
blueX = 0.15F;
blueY = 0.06F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
case ObuColorPrimaries.Bt601:
case ObuColorPrimaries.Smpte240:
redX = 0.630F;
redY = 0.340F;
greenX = 0.310F;
greenY = 0.595F;
blueX = 0.155F;
blueY = 0.070F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
case ObuColorPrimaries.GenericFilm:
redX = 0.681F;
redY = 0.319F;
greenX = 0.243F;
greenY = 0.692F;
blueX = 0.145F;
blueY = 0.049F;
whiteX = 0.310F;
whiteY = 0.316F;
break;
case ObuColorPrimaries.Bt2020:
redX = 0.708F;
redY = 0.292F;
greenX = 0.170F;
greenY = 0.797F;
blueX = 0.131F;
blueY = 0.046F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
case ObuColorPrimaries.Xyz:
redX = 1F;
redY = 0F;
greenX = 0F;
greenY = 1F;
blueX = 0F;
blueY = 0F;
whiteX = 1F / 3F;
whiteY = 1F / 3F;
break;
case ObuColorPrimaries.Smpte431:
redX = 0.680F;
redY = 0.320F;
greenX = 0.265F;
greenY = 0.690F;
blueX = 0.150F;
blueY = 0.060F;
whiteX = 0.314F;
whiteY = 0.351F;
break;
case ObuColorPrimaries.Smpte432:
redX = 0.680F;
redY = 0.320F;
greenX = 0.265F;
greenY = 0.690F;
blueX = 0.150F;
blueY = 0.060F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
case ObuColorPrimaries.Ebu3213:
redX = 0.630F;
redY = 0.340F;
greenX = 0.295F;
greenY = 0.605F;
blueX = 0.155F;
blueY = 0.077F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
default:
// Unspecified and reserved code points have no chromaticities to derive. Match libavif's established
// still-image fallback so such files retain the same deterministic BT.709 interpretation.
redX = 0.64F;
redY = 0.33F;
greenX = 0.30F;
greenY = 0.60F;
blueX = 0.15F;
blueY = 0.06F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
}
float redZ = 1F - (redX + redY);
float greenZ = 1F - (greenX + greenY);
float blueZ = 1F - (blueX + blueY);
float whiteZ = 1F - (whiteX + whiteY);
// H.273 equations 39 and 40 solve the RGB-to-XYZ primary matrix at the signaled white point. Keeping
// the expanded determinant matches libavif and avoids introducing a general matrix inversion dependency.
float denominator = whiteY *
((redX * ((greenY * blueZ) - (blueY * greenZ))) +
(greenX * ((blueY * redZ) - (redY * blueZ))) +
(blueX * ((redY * greenZ) - (greenY * redZ))));
kr = (redY *
((whiteX * ((greenY * blueZ) - (blueY * greenZ))) +
(whiteY * ((blueX * greenZ) - (greenX * blueZ))) +
(whiteZ * ((greenX * blueY) - (blueX * greenY))))) / denominator;
bool isMonochrome = frameBuffer.ColorFormat == Av1ColorFormat.Yuv400;
kb = (blueY *
((whiteX * ((redY * greenZ) - (greenY * redZ))) +
(whiteY * ((greenX * redZ) - (redX * greenZ))) +
(whiteZ * ((redX * greenY) - (greenX * redY))))) / denominator;
return HeifColorConversionParameters.Create(
(CicpColorPrimaries)(byte)frameBuffer.ColorConfig.ColorPrimaries,
(CicpTransferCharacteristics)(byte)frameBuffer.ColorConfig.TransferCharacteristics,
(CicpMatrixCoefficients)(byte)frameBuffer.ColorConfig.MatrixCoefficients,
frameBuffer.ColorConfig.ColorRange,
frameBuffer.BitDepth.GetBitCount(),
frameBuffer.BitDepth.GetBitCount(),
isMonochrome,
frameBuffer.ColorFormat == Av1ColorFormat.Yuv444,
out mode);
}
/// <summary>
@ -435,43 +173,4 @@ internal static partial class Av1YuvConverter
upper = isOdd ? Math.Min(sample + 1, maximum) : sample;
upperWeight = isOdd ? 2 : 0;
}
/// <summary>
/// Reads an 8-bit or 16-bit unsigned sample without introducing a separate conversion buffer.
/// </summary>
/// <typeparam name="TSample">The encoded sample type.</typeparam>
/// <param name="source">The source samples.</param>
/// <param name="index">The sample index.</param>
/// <returns>The sample value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float GetSample<TSample>(ReadOnlySpan<TSample> source, int index)
where TSample : unmanaged
{
ref TSample sample = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), index);
return typeof(TSample) == typeof(byte)
? Unsafe.As<TSample, byte>(ref sample)
: Unsafe.As<TSample, ushort>(ref sample);
}
/// <summary>
/// Rounds and clamps a conversion result to the encoded sample range.
/// </summary>
/// <typeparam name="TSample">The encoded sample type.</typeparam>
/// <param name="value">The conversion result.</param>
/// <param name="maximum">The largest encoded sample value.</param>
/// <returns>The bounded sample.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TSample ToSample<TSample>(float value, float maximum)
where TSample : unmanaged
{
int sample = Numerics.Clamp((int)MathF.Round(value, MidpointRounding.AwayFromZero), 0, (int)maximum);
if (typeof(TSample) == typeof(byte))
{
byte result = (byte)sample;
return Unsafe.As<byte, TSample>(ref result);
}
ushort highBitDepthResult = (ushort)sample;
return Unsafe.As<ushort, TSample>(ref highBitDepthResult);
}
}

498
src/ImageSharp/Formats/Heif/Color/HeifColorConversionParameters.cs

@ -0,0 +1,498 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
/// <summary>
/// Stores the resolved H.273 values shared by every scalar and SIMD lane.
/// </summary>
internal readonly struct HeifColorConversionParameters
{
/// <summary>
/// Initializes a new instance of the <see cref="HeifColorConversionParameters"/> 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>
/// <param name="lumaSampleMaximum">The largest encoded luma sample value.</param>
/// <param name="chromaSampleMaximum">The largest encoded chroma sample value.</param>
/// <param name="rgbBias">The reversible transform's RGB code-value bias.</param>
/// <param name="rgbScale">The reversible transform's RGB code-value range.</param>
/// <param name="rgbSampleMaximum">The largest reversible transform RGB code value.</param>
public HeifColorConversionParameters(
float kr,
float kg,
float kb,
CicpTransferCharacteristics transferCharacteristics,
in HeifConstantLuminanceScales constantLuminanceScales,
float lumaBias,
float lumaScale,
float chromaBias,
float chromaScale,
float lumaSampleMaximum,
float chromaSampleMaximum,
float rgbBias,
float rgbScale,
float rgbSampleMaximum)
{
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;
this.LumaSampleMaximum = lumaSampleMaximum;
this.ChromaSampleMaximum = chromaSampleMaximum;
this.RgbBias = rgbBias;
this.RgbScale = rgbScale;
this.RgbSampleMaximum = rgbSampleMaximum;
}
/// <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 CicpTransferCharacteristics TransferCharacteristics { get; }
/// <summary>
/// Gets the constant-luminance chroma scales.
/// </summary>
public HeifConstantLuminanceScales 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>
/// Gets the largest encoded luma sample value.
/// </summary>
public float LumaSampleMaximum { get; }
/// <summary>
/// Gets the largest encoded chroma sample value.
/// </summary>
public float ChromaSampleMaximum { get; }
/// <summary>
/// Gets the reversible transform's RGB code-value bias.
/// </summary>
public float RgbBias { get; }
/// <summary>
/// Gets the reversible transform's RGB code-value range.
/// </summary>
public float RgbScale { get; }
/// <summary>
/// Gets the largest reversible transform RGB code value.
/// </summary>
public float RgbSampleMaximum { get; }
/// <summary>
/// Resolves the H.273 matrix coefficients and sample ranges shared by HEVC and AV1 image items.
/// </summary>
/// <param name="colorPrimaries">The H.273 color-primary code point.</param>
/// <param name="transferCharacteristics">The H.273 transfer-characteristic code point.</param>
/// <param name="matrixCoefficients">The H.273 matrix-coefficient code point.</param>
/// <param name="isFullRange">Whether encoded components use their complete numeric range.</param>
/// <param name="lumaBitDepth">The encoded luma precision in bits.</param>
/// <param name="chromaBitDepth">The encoded chroma precision in bits.</param>
/// <param name="isMonochrome">Whether the image contains only luma samples.</param>
/// <param name="hasFullResolutionChroma">Whether both chroma planes have luma resolution.</param>
/// <param name="mode">The resolved conversion operation.</param>
/// <returns>The immutable values used by each scalar and SIMD conversion lane.</returns>
public static HeifColorConversionParameters Create(
CicpColorPrimaries colorPrimaries,
CicpTransferCharacteristics transferCharacteristics,
CicpMatrixCoefficients matrixCoefficients,
bool isFullRange,
int lumaBitDepth,
int chromaBitDepth,
bool isMonochrome,
bool hasFullResolutionChroma,
out HeifColorConversionMode mode)
{
mode = HeifColorConversionMode.Coefficients;
float kr = 0F;
float kb = 0F;
// H.273 assigns one closed arithmetic operation to each non-reserved matrix code point. Resolving the
// operation once keeps both codec wrappers and every vector lane free from per-sample format dispatch.
switch (matrixCoefficients)
{
case CicpMatrixCoefficients.Identity:
mode = HeifColorConversionMode.Identity;
break;
case CicpMatrixCoefficients.ItuRBt709_6:
kr = 0.2126F;
kb = 0.0722F;
break;
case CicpMatrixCoefficients.Fcc47:
kr = 0.30F;
kb = 0.11F;
break;
case CicpMatrixCoefficients.ItuRBt601_7_625:
case CicpMatrixCoefficients.ItuRBt601_7_525:
case CicpMatrixCoefficients.Unspecified:
kr = 0.299F;
kb = 0.114F;
break;
case CicpMatrixCoefficients.SmpteSt240:
kr = 0.212F;
kb = 0.087F;
break;
case CicpMatrixCoefficients.YCgCo:
mode = HeifColorConversionMode.YCgCo;
break;
case CicpMatrixCoefficients.ItuRBt2020_2_Ncl:
kr = 0.2627F;
kb = 0.0593F;
break;
case CicpMatrixCoefficients.ItuRBt2020_2_Cl:
mode = HeifColorConversionMode.ConstantLuminance;
kr = 0.2627F;
kb = 0.0593F;
break;
case CicpMatrixCoefficients.SmpteSt2085:
mode = HeifColorConversionMode.Smpte2085;
break;
case CicpMatrixCoefficients.ChromaDerivedNcl:
GetChromaticityDerivedCoefficients(colorPrimaries, out kr, out kb);
break;
case CicpMatrixCoefficients.ChromaDerivedCl:
mode = HeifColorConversionMode.ConstantLuminance;
GetChromaticityDerivedCoefficients(colorPrimaries, out kr, out kb);
break;
case CicpMatrixCoefficients.ICtCp:
mode = HeifColorConversionMode.ICtCp;
break;
case CicpMatrixCoefficients.IptC2:
mode = HeifColorConversionMode.IptC2;
break;
case CicpMatrixCoefficients.YCgCoRe:
case CicpMatrixCoefficients.YCgCoRo:
mode = HeifColorConversionMode.YCgCoReversible;
break;
default:
throw new InvalidImageContentException($"The image declares reserved H.273 matrix coefficients '{(byte)matrixCoefficients}'.");
}
bool requiresFullResolutionChroma = mode is HeifColorConversionMode.Identity or HeifColorConversionMode.YCgCoReversible;
if (requiresFullResolutionChroma && !isMonochrome && !hasFullResolutionChroma)
{
throw new InvalidImageContentException($"H.273 matrix coefficients '{matrixCoefficients}' require 4:4:4 sampling.");
}
bool requiresEqualBitDepth = mode is HeifColorConversionMode.Identity or HeifColorConversionMode.YCgCoReversible;
if (requiresEqualBitDepth && !isMonochrome && lumaBitDepth != chromaBitDepth)
{
throw new InvalidImageContentException($"H.273 matrix coefficients '{matrixCoefficients}' require equal component bit depths.");
}
float kg = 1F - kr - kb;
int lumaDepthScale = 1 << (lumaBitDepth - 8);
int chromaDepthScale = 1 << (chromaBitDepth - 8);
float lumaSampleMaximum = (1 << lumaBitDepth) - 1;
float chromaSampleMaximum = (1 << chromaBitDepth) - 1;
float chromaBias = 128F * chromaDepthScale;
float lumaBias = isFullRange ? 0F : 16F * lumaDepthScale;
float lumaScale = isFullRange ? lumaSampleMaximum : 219F * lumaDepthScale;
// Limited-range YCgCo first range-adjusts RGB through the luma range, so its difference components use
// 219 codes. Conventional YCbCr uses the independently specified 224-code chroma excursion.
float chromaScale = isFullRange
? chromaSampleMaximum
: (mode == HeifColorConversionMode.YCgCo ? 219F : 224F) * chromaDepthScale;
float rgbBias = 0F;
float rgbScale = 1F;
float rgbSampleMaximum = 1F;
if (mode == HeifColorConversionMode.YCgCoReversible)
{
int bitOffset = matrixCoefficients == CicpMatrixCoefficients.YCgCoRe ? 2 : 1;
int rgbBitDepth = lumaBitDepth - bitOffset;
float rgbDepthScale = MathF.ScaleB(1F, rgbBitDepth - 8);
rgbSampleMaximum = (1 << rgbBitDepth) - 1;
rgbBias = isFullRange ? 0F : 16F * rgbDepthScale;
rgbScale = isFullRange ? rgbSampleMaximum : 219F * rgbDepthScale;
// The reversible lifting transform works on integer code values. It performs RGB range adjustment
// internally, while the row traversal normalizes all three encoded components to their full code range.
lumaBias = 0F;
lumaScale = lumaSampleMaximum;
chromaScale = chromaSampleMaximum;
}
HeifConstantLuminanceScales constantLuminanceScales = mode == HeifColorConversionMode.ConstantLuminance
? new HeifConstantLuminanceScales(transferCharacteristics, kr, kb)
: default;
return new HeifColorConversionParameters(
kr,
kg,
kb,
transferCharacteristics,
in constantLuminanceScales,
lumaBias,
lumaScale,
chromaBias,
chromaScale,
lumaSampleMaximum,
chromaSampleMaximum,
rgbBias,
rgbScale,
rgbSampleMaximum);
}
/// <summary>
/// Computes the luma coefficients defined by an H.273 primary-chromaticity code point.
/// </summary>
/// <param name="colorPrimaries">The H.273 color-primary code point.</param>
/// <param name="kr">The resulting red luma coefficient.</param>
/// <param name="kb">The resulting blue luma coefficient.</param>
private static void GetChromaticityDerivedCoefficients(CicpColorPrimaries colorPrimaries, out float kr, out float kb)
{
float redX;
float redY;
float greenX;
float greenY;
float blueX;
float blueY;
float whiteX;
float whiteY;
switch (colorPrimaries)
{
case CicpColorPrimaries.ItuRBt470_6M:
redX = 0.67F;
redY = 0.33F;
greenX = 0.21F;
greenY = 0.71F;
blueX = 0.14F;
blueY = 0.08F;
whiteX = 0.310F;
whiteY = 0.316F;
break;
case CicpColorPrimaries.ItuRBt601_7_625:
redX = 0.64F;
redY = 0.33F;
greenX = 0.29F;
greenY = 0.60F;
blueX = 0.15F;
blueY = 0.06F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
case CicpColorPrimaries.ItuRBt601_7_525:
case CicpColorPrimaries.SmpteSt240:
redX = 0.630F;
redY = 0.340F;
greenX = 0.310F;
greenY = 0.595F;
blueX = 0.155F;
blueY = 0.070F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
case CicpColorPrimaries.GenericFilm:
redX = 0.681F;
redY = 0.319F;
greenX = 0.243F;
greenY = 0.692F;
blueX = 0.145F;
blueY = 0.049F;
whiteX = 0.310F;
whiteY = 0.316F;
break;
case CicpColorPrimaries.ItuRBt2020_2:
redX = 0.708F;
redY = 0.292F;
greenX = 0.170F;
greenY = 0.797F;
blueX = 0.131F;
blueY = 0.046F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
case CicpColorPrimaries.SmpteSt428_1:
redX = 1F;
redY = 0F;
greenX = 0F;
greenY = 1F;
blueX = 0F;
blueY = 0F;
whiteX = 1F / 3F;
whiteY = 1F / 3F;
break;
case CicpColorPrimaries.SmpteRp431_2:
redX = 0.680F;
redY = 0.320F;
greenX = 0.265F;
greenY = 0.690F;
blueX = 0.150F;
blueY = 0.060F;
whiteX = 0.314F;
whiteY = 0.351F;
break;
case CicpColorPrimaries.SmpteEg432_1:
redX = 0.680F;
redY = 0.320F;
greenX = 0.265F;
greenY = 0.690F;
blueX = 0.150F;
blueY = 0.060F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
case CicpColorPrimaries.EbuTech3213E:
redX = 0.630F;
redY = 0.340F;
greenX = 0.295F;
greenY = 0.605F;
blueX = 0.155F;
blueY = 0.077F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
default:
// Unspecified primaries cannot define a chromaticity-derived matrix. The established libavif
// behavior supplies BT.709/D65 so the image has one deterministic interpretation.
redX = 0.64F;
redY = 0.33F;
greenX = 0.30F;
greenY = 0.60F;
blueX = 0.15F;
blueY = 0.06F;
whiteX = 0.3127F;
whiteY = 0.3290F;
break;
}
float redZ = 1F - (redX + redY);
float greenZ = 1F - (greenX + greenY);
float blueZ = 1F - (blueX + blueY);
float whiteZ = 1F - (whiteX + whiteY);
// H.273 equations 39 and 40 solve the RGB-to-XYZ primary matrix at the signaled white point. Expanding the
// determinant avoids a general matrix inversion and gives both codec paths the same rounding sequence.
float denominator = whiteY *
((redX * ((greenY * blueZ) - (blueY * greenZ))) +
(greenX * ((blueY * redZ) - (redY * blueZ))) +
(blueX * ((redY * greenZ) - (greenY * redZ))));
kr = (redY *
((whiteX * ((greenY * blueZ) - (blueY * greenZ))) +
(whiteY * ((blueX * greenZ) - (greenX * blueZ))) +
(whiteZ * ((greenX * blueY) - (blueX * greenY))))) / denominator;
kb = (blueY *
((whiteX * ((redY * greenZ) - (greenY * redZ))) +
(whiteY * ((greenX * redZ) - (redX * greenZ))) +
(whiteZ * ((redX * greenY) - (greenX * redY))))) / denominator;
}
}
/// <summary>
/// Stores the H.273 chroma normalization constants for constant-luminance conversion.
/// </summary>
internal readonly struct HeifConstantLuminanceScales
{
/// <summary>
/// Initializes a new instance of the <see cref="HeifConstantLuminanceScales"/> 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 HeifConstantLuminanceScales(CicpTransferCharacteristics transferCharacteristics, float kr, float kb)
{
this.NegativeBlue = HeifTransferFunctions.ToGamma(transferCharacteristics, 1F - kb);
this.PositiveBlue = 1F - HeifTransferFunctions.ToGamma(transferCharacteristics, kb);
this.NegativeRed = HeifTransferFunctions.ToGamma(transferCharacteristics, 1F - kr);
this.PositiveRed = 1F - HeifTransferFunctions.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; }
}

22
src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.CoefficientOperator.cs → src/ImageSharp/Formats/Heif/Color/HeifColorConverter.CoefficientOperator.cs

@ -4,21 +4,21 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
internal abstract partial class Av1ColorConverterBase
internal abstract partial class HeifColorConverterBase
{
/// <summary>
/// Implements coefficient-based YCbCr conversion for scalar and SIMD lanes.
/// </summary>
internal readonly struct Av1CoefficientColorOperator : IAv1ColorOperator
internal readonly struct HeifCoefficientColorOperator : IHeifColorOperator
{
/// <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)
public static void ConvertToRgb(ref float y, ref float cb, ref float cr, in HeifColorConversionParameters 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.
@ -33,7 +33,7 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in HeifColorConversionParameters parameters)
{
Vector128<float> r = Vector128.MultiplyAddEstimate(Vector128.Create(parameters.RedChromaScale), cr, y);
Vector128<float> g = Vector128.MultiplyAddEstimate(
@ -49,7 +49,7 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in HeifColorConversionParameters parameters)
{
Vector256<float> r = Vector256.MultiplyAddEstimate(Vector256.Create(parameters.RedChromaScale), cr, y);
Vector256<float> g = Vector256.MultiplyAddEstimate(
@ -65,7 +65,7 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in HeifColorConversionParameters parameters)
{
Vector512<float> r = Vector512.MultiplyAddEstimate(Vector512.Create(parameters.RedChromaScale), cr, y);
Vector512<float> g = Vector512.MultiplyAddEstimate(
@ -85,7 +85,7 @@ internal abstract partial class Av1ColorConverterBase
float r,
float g,
float b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out float y,
out float cb,
out float cr)
@ -102,7 +102,7 @@ internal abstract partial class Av1ColorConverterBase
Vector128<float> r,
Vector128<float> g,
Vector128<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector128<float> y,
out Vector128<float> cb,
out Vector128<float> cr)
@ -121,7 +121,7 @@ internal abstract partial class Av1ColorConverterBase
Vector256<float> r,
Vector256<float> g,
Vector256<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector256<float> y,
out Vector256<float> cb,
out Vector256<float> cr)
@ -140,7 +140,7 @@ internal abstract partial class Av1ColorConverterBase
Vector512<float> r,
Vector512<float> g,
Vector512<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector512<float> y,
out Vector512<float> cb,
out Vector512<float> cr)

102
src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ConstantLuminanceOperator.cs → src/ImageSharp/Formats/Heif/Color/HeifColorConverter.ConstantLuminanceOperator.cs

@ -4,42 +4,42 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
internal abstract partial class Av1ColorConverterBase
internal abstract partial class HeifColorConverterBase
{
/// <summary>
/// Implements H.273 constant-luminance conversion for scalar and SIMD lanes.
/// </summary>
internal readonly struct Av1ConstantLuminanceColorOperator : IAv1ColorOperator
internal readonly struct HeifConstantLuminanceColorOperator : IHeifColorOperator
{
/// <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)
public static void ConvertToRgb(ref float y, ref float cb, ref float cr, in HeifColorConversionParameters parameters)
{
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
HeifConstantLuminanceScales 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 linearY = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, y);
float linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue);
float linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed);
float linearGreen = (linearY - (parameters.Kr * linearRed) - (parameters.Kb * linearBlue)) / parameters.Kg;
y = nonlinearRed;
cb = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
cb = HeifTransferFunctions.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)
public static void ConvertToRgb(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in HeifColorConversionParameters parameters)
{
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
Vector128<float> blueScale = Vector128.ConditionalSelect(
Vector128.LessThanOrEqual(cb, Vector128<float>.Zero),
Vector128.Create(scales.NegativeBlue),
@ -50,23 +50,23 @@ internal abstract partial class Av1ColorConverterBase
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> linearY = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, y);
Vector128<float> linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue);
Vector128<float> linearRed = HeifTransferFunctions.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);
cb = HeifTransferFunctions.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)
public static void ConvertToRgb(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in HeifColorConversionParameters parameters)
{
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
Vector256<float> blueScale = Vector256.ConditionalSelect(
Vector256.LessThanOrEqual(cb, Vector256<float>.Zero),
Vector256.Create(scales.NegativeBlue),
@ -77,23 +77,23 @@ internal abstract partial class Av1ColorConverterBase
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> linearY = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, y);
Vector256<float> linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue);
Vector256<float> linearRed = HeifTransferFunctions.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);
cb = HeifTransferFunctions.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)
public static void ConvertToRgb(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in HeifColorConversionParameters parameters)
{
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
Vector512<float> blueScale = Vector512.ConditionalSelect(
Vector512.LessThanOrEqual(cb, Vector512<float>.Zero),
Vector512.Create(scales.NegativeBlue),
@ -104,15 +104,15 @@ internal abstract partial class Av1ColorConverterBase
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> linearY = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, y);
Vector512<float> linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue);
Vector512<float> linearRed = HeifTransferFunctions.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);
cb = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
cr = nonlinearBlue;
}
@ -122,21 +122,21 @@ internal abstract partial class Av1ColorConverterBase
float r,
float g,
float b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters 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 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, r);
float linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, g);
float linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, b);
float linearY = (parameters.Kr * linearRed) + (parameters.Kg * linearGreen) + (parameters.Kb * linearBlue);
y = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY);
y = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY);
float blueDifference = b - y;
float redDifference = r - y;
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
cb = blueDifference / (2F * (blueDifference <= 0F ? scales.NegativeBlue : scales.PositiveBlue));
cr = redDifference / (2F * (redDifference <= 0F ? scales.NegativeRed : scales.PositiveRed));
}
@ -147,22 +147,22 @@ internal abstract partial class Av1ColorConverterBase
Vector128<float> r,
Vector128<float> g,
Vector128<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters 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> linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, r);
Vector128<float> linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, g);
Vector128<float> linearBlue = HeifTransferFunctions.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);
y = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY);
Vector128<float> blueDifference = b - y;
Vector128<float> redDifference = r - y;
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
Vector128<float> blueScale = Vector128.ConditionalSelect(
Vector128.LessThanOrEqual(blueDifference, Vector128<float>.Zero),
Vector128.Create(scales.NegativeBlue),
@ -182,22 +182,22 @@ internal abstract partial class Av1ColorConverterBase
Vector256<float> r,
Vector256<float> g,
Vector256<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters 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> linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, r);
Vector256<float> linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, g);
Vector256<float> linearBlue = HeifTransferFunctions.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);
y = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY);
Vector256<float> blueDifference = b - y;
Vector256<float> redDifference = r - y;
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
Vector256<float> blueScale = Vector256.ConditionalSelect(
Vector256.LessThanOrEqual(blueDifference, Vector256<float>.Zero),
Vector256.Create(scales.NegativeBlue),
@ -217,22 +217,22 @@ internal abstract partial class Av1ColorConverterBase
Vector512<float> r,
Vector512<float> g,
Vector512<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters 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> linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, r);
Vector512<float> linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, g);
Vector512<float> linearBlue = HeifTransferFunctions.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);
y = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY);
Vector512<float> blueDifference = b - y;
Vector512<float> redDifference = r - y;
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
Vector512<float> blueScale = Vector512.ConditionalSelect(
Vector512.LessThanOrEqual(blueDifference, Vector512<float>.Zero),
Vector512.Create(scales.NegativeBlue),

136
src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ICtCpOperator.cs → src/ImageSharp/Formats/Heif/Color/HeifColorConverter.ICtCpOperator.cs

@ -3,16 +3,16 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
internal abstract partial class Av1ColorConverterBase
internal abstract partial class HeifColorConverterBase
{
/// <summary>
/// Implements BT.2100 ICtCp conversion for scalar and SIMD lanes.
/// </summary>
internal readonly struct Av1ICtCpColorOperator : IAv1ColorOperator
internal readonly struct HeifICtCpColorOperator : IHeifColorOperator
{
/// <summary>
/// The PQ Ct contribution to nonlinear L.
@ -209,9 +209,9 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref float intensity, ref float ct, ref float cp, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref float intensity, ref float ct, ref float cp, in HeifColorConversionParameters parameters)
{
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg;
bool isHlg = parameters.TransferCharacteristics == CicpTransferCharacteristics.AribStdB67;
float ctToL = isHlg ? HlgCtToL : PqCtToL;
float cpToL = isHlg ? HlgCpToL : PqCpToL;
@ -220,16 +220,16 @@ internal abstract partial class Av1ColorConverterBase
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 linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
float linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
float linearS = HeifTransferFunctions.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);
intensity = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed);
ct = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
cp = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
}
/// <inheritdoc/>
@ -238,9 +238,9 @@ internal abstract partial class Av1ColorConverterBase
ref Vector128<float> intensity,
ref Vector128<float> ct,
ref Vector128<float> cp,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg;
bool isHlg = parameters.TransferCharacteristics == CicpTransferCharacteristics.AribStdB67;
Vector128<float> ctContribution = Vector128.Create(isHlg ? HlgCtToL : PqCtToL) * ct;
Vector128<float> cpContribution = Vector128.Create(isHlg ? HlgCpToL : PqCpToL) * cp;
Vector128<float> nonlinearL = intensity + ctContribution + cpContribution;
@ -249,9 +249,9 @@ internal abstract partial class Av1ColorConverterBase
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> linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
Vector128<float> linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
Vector128<float> linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS);
Vector128<float> linearRed = Vector128.MultiplyAddEstimate(
Vector128.Create(SToRed),
linearS,
@ -265,9 +265,9 @@ internal abstract partial class Av1ColorConverterBase
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);
intensity = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed);
ct = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
cp = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
}
/// <inheritdoc/>
@ -276,9 +276,9 @@ internal abstract partial class Av1ColorConverterBase
ref Vector256<float> intensity,
ref Vector256<float> ct,
ref Vector256<float> cp,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg;
bool isHlg = parameters.TransferCharacteristics == CicpTransferCharacteristics.AribStdB67;
Vector256<float> ctContribution = Vector256.Create(isHlg ? HlgCtToL : PqCtToL) * ct;
Vector256<float> cpContribution = Vector256.Create(isHlg ? HlgCpToL : PqCpToL) * cp;
Vector256<float> nonlinearL = intensity + ctContribution + cpContribution;
@ -287,9 +287,9 @@ internal abstract partial class Av1ColorConverterBase
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> linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
Vector256<float> linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
Vector256<float> linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS);
Vector256<float> linearRed = Vector256.MultiplyAddEstimate(
Vector256.Create(SToRed),
linearS,
@ -303,9 +303,9 @@ internal abstract partial class Av1ColorConverterBase
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);
intensity = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed);
ct = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
cp = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
}
/// <inheritdoc/>
@ -314,9 +314,9 @@ internal abstract partial class Av1ColorConverterBase
ref Vector512<float> intensity,
ref Vector512<float> ct,
ref Vector512<float> cp,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg;
bool isHlg = parameters.TransferCharacteristics == CicpTransferCharacteristics.AribStdB67;
Vector512<float> ctContribution = Vector512.Create(isHlg ? HlgCtToL : PqCtToL) * ct;
Vector512<float> cpContribution = Vector512.Create(isHlg ? HlgCpToL : PqCpToL) * cp;
Vector512<float> nonlinearL = intensity + ctContribution + cpContribution;
@ -325,9 +325,9 @@ internal abstract partial class Av1ColorConverterBase
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> linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
Vector512<float> linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
Vector512<float> linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS);
Vector512<float> linearRed = Vector512.MultiplyAddEstimate(
Vector512.Create(SToRed),
linearS,
@ -341,9 +341,9 @@ internal abstract partial class Av1ColorConverterBase
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);
intensity = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed);
ct = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
cp = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
}
/// <inheritdoc/>
@ -352,27 +352,27 @@ internal abstract partial class Av1ColorConverterBase
float red,
float green,
float blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters 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(
float linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red);
float linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green);
float linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue);
float nonlinearL = HeifTransferFunctions.ToGamma(
parameters.TransferCharacteristics,
(RedToL * linearRed) + (GreenToL * linearGreen) + (BlueToL * linearBlue));
float nonlinearM = Av1TransferFunctions.ToGamma(
float nonlinearM = HeifTransferFunctions.ToGamma(
parameters.TransferCharacteristics,
(RedToM * linearRed) + (GreenToM * linearGreen) + (BlueToM * linearBlue));
float nonlinearS = Av1TransferFunctions.ToGamma(
float nonlinearS = HeifTransferFunctions.ToGamma(
parameters.TransferCharacteristics,
(RedToS * linearRed) + (GreenToS * linearGreen) + (BlueToS * linearBlue));
intensity = 0.5F * (nonlinearL + nonlinearM);
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg;
bool isHlg = parameters.TransferCharacteristics == CicpTransferCharacteristics.AribStdB67;
ct = isHlg
? (HlgLToCt * nonlinearL) + (HlgMToCt * nonlinearM) + (HlgSToCt * nonlinearS)
: (PqLToCt * nonlinearL) + (PqMToCt * nonlinearM) + (PqSToCt * nonlinearS);
@ -388,14 +388,14 @@ internal abstract partial class Av1ColorConverterBase
Vector128<float> red,
Vector128<float> green,
Vector128<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters 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> linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red);
Vector128<float> linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green);
Vector128<float> linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue);
Vector128<float> linearL = Vector128.MultiplyAddEstimate(
Vector128.Create(RedToL),
linearRed,
@ -408,11 +408,11 @@ internal abstract partial class Av1ColorConverterBase
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);
Vector128<float> nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
Vector128<float> nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
Vector128<float> nonlinearS = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS);
intensity = Vector128.Create(0.5F) * (nonlinearL + nonlinearM);
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg;
bool isHlg = parameters.TransferCharacteristics == CicpTransferCharacteristics.AribStdB67;
float lToCt = isHlg ? HlgLToCt : PqLToCt;
float mToCt = isHlg ? HlgMToCt : PqMToCt;
float sToCt = isHlg ? HlgSToCt : PqSToCt;
@ -435,14 +435,14 @@ internal abstract partial class Av1ColorConverterBase
Vector256<float> red,
Vector256<float> green,
Vector256<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters 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> linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red);
Vector256<float> linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green);
Vector256<float> linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue);
Vector256<float> linearL = Vector256.MultiplyAddEstimate(
Vector256.Create(RedToL),
linearRed,
@ -455,11 +455,11 @@ internal abstract partial class Av1ColorConverterBase
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);
Vector256<float> nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
Vector256<float> nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
Vector256<float> nonlinearS = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS);
intensity = Vector256.Create(0.5F) * (nonlinearL + nonlinearM);
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg;
bool isHlg = parameters.TransferCharacteristics == CicpTransferCharacteristics.AribStdB67;
float lToCt = isHlg ? HlgLToCt : PqLToCt;
float mToCt = isHlg ? HlgMToCt : PqMToCt;
float sToCt = isHlg ? HlgSToCt : PqSToCt;
@ -482,14 +482,14 @@ internal abstract partial class Av1ColorConverterBase
Vector512<float> red,
Vector512<float> green,
Vector512<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters 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> linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red);
Vector512<float> linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green);
Vector512<float> linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue);
Vector512<float> linearL = Vector512.MultiplyAddEstimate(
Vector512.Create(RedToL),
linearRed,
@ -502,11 +502,11 @@ internal abstract partial class Av1ColorConverterBase
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);
Vector512<float> nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
Vector512<float> nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
Vector512<float> nonlinearS = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS);
intensity = Vector512.Create(0.5F) * (nonlinearL + nonlinearM);
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg;
bool isHlg = parameters.TransferCharacteristics == CicpTransferCharacteristics.AribStdB67;
float lToCt = isHlg ? HlgLToCt : PqLToCt;
float mToCt = isHlg ? HlgMToCt : PqMToCt;
float sToCt = isHlg ? HlgSToCt : PqSToCt;

22
src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.IdentityOperator.cs → src/ImageSharp/Formats/Heif/Color/HeifColorConverter.IdentityOperator.cs

@ -4,21 +4,21 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
internal abstract partial class Av1ColorConverterBase
internal abstract partial class HeifColorConverterBase
{
/// <summary>
/// Implements direct G, B, and R plane mapping for scalar and SIMD lanes.
/// </summary>
internal readonly struct Av1IdentityColorOperator : IAv1ColorOperator
internal readonly struct HeifIdentityColorOperator : IHeifColorOperator
{
/// <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)
public static void ConvertToRgb(ref float green, ref float blue, ref float red, in HeifColorConversionParameters 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.
@ -34,7 +34,7 @@ internal abstract partial class Av1ColorConverterBase
ref Vector128<float> green,
ref Vector128<float> blue,
ref Vector128<float> red,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
Vector128<float> g = green;
green = red;
@ -48,7 +48,7 @@ internal abstract partial class Av1ColorConverterBase
ref Vector256<float> green,
ref Vector256<float> blue,
ref Vector256<float> red,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
Vector256<float> g = green;
green = red;
@ -62,7 +62,7 @@ internal abstract partial class Av1ColorConverterBase
ref Vector512<float> green,
ref Vector512<float> blue,
ref Vector512<float> red,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
Vector512<float> g = green;
green = red;
@ -76,7 +76,7 @@ internal abstract partial class Av1ColorConverterBase
float red,
float green,
float blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out float component0,
out float component1,
out float component2)
@ -93,7 +93,7 @@ internal abstract partial class Av1ColorConverterBase
Vector128<float> red,
Vector128<float> green,
Vector128<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector128<float> component0,
out Vector128<float> component1,
out Vector128<float> component2)
@ -109,7 +109,7 @@ internal abstract partial class Av1ColorConverterBase
Vector256<float> red,
Vector256<float> green,
Vector256<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector256<float> component0,
out Vector256<float> component1,
out Vector256<float> component2)
@ -125,7 +125,7 @@ internal abstract partial class Av1ColorConverterBase
Vector512<float> red,
Vector512<float> green,
Vector512<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector512<float> component0,
out Vector512<float> component1,
out Vector512<float> component2)

118
src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.IptC2Operator.cs → src/ImageSharp/Formats/Heif/Color/HeifColorConverter.IptC2Operator.cs

@ -4,14 +4,14 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
internal abstract partial class Av1ColorConverterBase
internal abstract partial class HeifColorConverterBase
{
/// <summary>
/// Implements IPT-C2 conversion for scalar and SIMD lanes.
/// </summary>
internal readonly struct Av1IptC2ColorOperator : IAv1ColorOperator
internal readonly struct HeifIptC2ColorOperator : IHeifColorOperator
{
/// <summary>
/// The linear red contribution to L.
@ -183,23 +183,23 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref float intensity, ref float protan, ref float tritan, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref float intensity, ref float protan, ref float tritan, in HeifColorConversionParameters parameters)
{
// IPT-C2 stores opponent axes around intensity in nonlinear LMS. Undo both matrices around the
// signaled transfer function so the final RGB values remain in the source signal domain.
float nonlinearL = intensity + (ProtanToL * protan) + (TritanToL * tritan);
float nonlinearM = intensity + (ProtanToM * protan) + (TritanToM * tritan);
float nonlinearS = intensity + (ProtanToS * protan) + (TritanToS * tritan);
float linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
float linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
float linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS);
float linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
float linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
float linearS = HeifTransferFunctions.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);
protan = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
tritan = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
intensity = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed);
protan = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
tritan = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
}
/// <inheritdoc/>
@ -208,7 +208,7 @@ internal abstract partial class Av1ColorConverterBase
ref Vector128<float> intensity,
ref Vector128<float> protan,
ref Vector128<float> tritan,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
Vector128<float> nonlinearL = Vector128.MultiplyAddEstimate(
Vector128.Create(TritanToL),
@ -225,9 +225,9 @@ internal abstract partial class Av1ColorConverterBase
tritan,
Vector128.MultiplyAddEstimate(Vector128.Create(ProtanToS), protan, 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> linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
Vector128<float> linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
Vector128<float> linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS);
Vector128<float> linearRed = Vector128.MultiplyAddEstimate(
Vector128.Create(SToRed), linearS, Vector128.MultiplyAddEstimate(Vector128.Create(MToRed), linearM, Vector128.Create(LToRed) * linearL));
@ -237,9 +237,9 @@ internal abstract partial class Av1ColorConverterBase
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);
protan = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
tritan = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
intensity = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed);
protan = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
tritan = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
}
/// <inheritdoc/>
@ -248,7 +248,7 @@ internal abstract partial class Av1ColorConverterBase
ref Vector256<float> intensity,
ref Vector256<float> protan,
ref Vector256<float> tritan,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
Vector256<float> nonlinearL = Vector256.MultiplyAddEstimate(
Vector256.Create(TritanToL),
@ -265,9 +265,9 @@ internal abstract partial class Av1ColorConverterBase
tritan,
Vector256.MultiplyAddEstimate(Vector256.Create(ProtanToS), protan, 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> linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
Vector256<float> linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
Vector256<float> linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS);
Vector256<float> linearRed = Vector256.MultiplyAddEstimate(
Vector256.Create(SToRed), linearS, Vector256.MultiplyAddEstimate(Vector256.Create(MToRed), linearM, Vector256.Create(LToRed) * linearL));
@ -277,9 +277,9 @@ internal abstract partial class Av1ColorConverterBase
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);
protan = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
tritan = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
intensity = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed);
protan = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
tritan = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
}
/// <inheritdoc/>
@ -288,7 +288,7 @@ internal abstract partial class Av1ColorConverterBase
ref Vector512<float> intensity,
ref Vector512<float> protan,
ref Vector512<float> tritan,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
Vector512<float> nonlinearL = Vector512.MultiplyAddEstimate(
Vector512.Create(TritanToL),
@ -305,9 +305,9 @@ internal abstract partial class Av1ColorConverterBase
tritan,
Vector512.MultiplyAddEstimate(Vector512.Create(ProtanToS), protan, 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> linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
Vector512<float> linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
Vector512<float> linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS);
Vector512<float> linearRed = Vector512.MultiplyAddEstimate(
Vector512.Create(SToRed), linearS, Vector512.MultiplyAddEstimate(Vector512.Create(MToRed), linearM, Vector512.Create(LToRed) * linearL));
@ -317,9 +317,9 @@ internal abstract partial class Av1ColorConverterBase
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);
protan = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
tritan = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
intensity = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed);
protan = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen);
tritan = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue);
}
/// <inheritdoc/>
@ -328,25 +328,25 @@ internal abstract partial class Av1ColorConverterBase
float red,
float green,
float blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out float intensity,
out float protan,
out float tritan)
{
// The encoded RGB signal is linearized before the LMS matrix, then the signaled transfer function
// is reapplied to each LMS component before the fixed IPT-C2 opponent matrix.
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(
float linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red);
float linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green);
float linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue);
float nonlinearL = HeifTransferFunctions.ToGamma(
parameters.TransferCharacteristics,
(RedToL * linearRed) + (GreenToL * linearGreen) + (BlueToL * linearBlue));
float nonlinearM = Av1TransferFunctions.ToGamma(
float nonlinearM = HeifTransferFunctions.ToGamma(
parameters.TransferCharacteristics,
(RedToM * linearRed) + (GreenToM * linearGreen) + (BlueToM * linearBlue));
float nonlinearS = Av1TransferFunctions.ToGamma(
float nonlinearS = HeifTransferFunctions.ToGamma(
parameters.TransferCharacteristics,
(RedToS * linearRed) + (GreenToS * linearGreen) + (BlueToS * linearBlue));
@ -361,14 +361,14 @@ internal abstract partial class Av1ColorConverterBase
Vector128<float> red,
Vector128<float> green,
Vector128<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector128<float> intensity,
out Vector128<float> protan,
out Vector128<float> tritan)
{
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> linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red);
Vector128<float> linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green);
Vector128<float> linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue);
Vector128<float> linearL = Vector128.MultiplyAddEstimate(
Vector128.Create(BlueToL),
linearBlue,
@ -384,9 +384,9 @@ internal abstract partial class Av1ColorConverterBase
linearBlue,
Vector128.MultiplyAddEstimate(Vector128.Create(GreenToS), linearGreen, Vector128.Create(RedToS) * linearRed));
Vector128<float> nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
Vector128<float> nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
Vector128<float> nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS);
Vector128<float> nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
Vector128<float> nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
Vector128<float> nonlinearS = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS);
intensity = Vector128.MultiplyAddEstimate(
Vector128.Create(SToIntensity),
nonlinearS,
@ -409,14 +409,14 @@ internal abstract partial class Av1ColorConverterBase
Vector256<float> red,
Vector256<float> green,
Vector256<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector256<float> intensity,
out Vector256<float> protan,
out Vector256<float> tritan)
{
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> linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red);
Vector256<float> linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green);
Vector256<float> linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue);
Vector256<float> linearL = Vector256.MultiplyAddEstimate(
Vector256.Create(BlueToL),
linearBlue,
@ -432,9 +432,9 @@ internal abstract partial class Av1ColorConverterBase
linearBlue,
Vector256.MultiplyAddEstimate(Vector256.Create(GreenToS), linearGreen, Vector256.Create(RedToS) * linearRed));
Vector256<float> nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
Vector256<float> nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
Vector256<float> nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS);
Vector256<float> nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
Vector256<float> nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
Vector256<float> nonlinearS = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS);
intensity = Vector256.MultiplyAddEstimate(
Vector256.Create(SToIntensity),
nonlinearS,
@ -457,14 +457,14 @@ internal abstract partial class Av1ColorConverterBase
Vector512<float> red,
Vector512<float> green,
Vector512<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector512<float> intensity,
out Vector512<float> protan,
out Vector512<float> tritan)
{
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> linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red);
Vector512<float> linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green);
Vector512<float> linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue);
Vector512<float> linearL = Vector512.MultiplyAddEstimate(
Vector512.Create(BlueToL),
linearBlue,
@ -480,9 +480,9 @@ internal abstract partial class Av1ColorConverterBase
linearBlue,
Vector512.MultiplyAddEstimate(Vector512.Create(GreenToS), linearGreen, Vector512.Create(RedToS) * linearRed));
Vector512<float> nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
Vector512<float> nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
Vector512<float> nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS);
Vector512<float> nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
Vector512<float> nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
Vector512<float> nonlinearS = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS);
intensity = Vector512.MultiplyAddEstimate(
Vector512.Create(SToIntensity),
nonlinearS,

44
src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.Operator.cs → src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Operator.cs

@ -5,17 +5,17 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
/// <content>
/// Provides the static operator contract and SIMD traversal used by AV1 color converters.
/// </content>
internal abstract partial class Av1ColorConverterBase
internal abstract partial class HeifColorConverterBase
{
/// <summary>
/// Defines color-model arithmetic for scalar and SIMD lanes in both conversion directions.
/// </summary>
internal interface IAv1ColorOperator
internal interface IHeifColorOperator
{
/// <summary>
/// Gets a value indicating whether chroma uses the luma range rather than the centered chroma range.
@ -33,7 +33,7 @@ internal abstract partial class Av1ColorConverterBase
ref float component0,
ref float component1,
ref float component2,
in Av1ColorConversionParameters parameters);
in HeifColorConversionParameters parameters);
/// <summary>
/// Converts four normalized AV1 samples to RGB.
@ -46,7 +46,7 @@ internal abstract partial class Av1ColorConverterBase
ref Vector128<float> component0,
ref Vector128<float> component1,
ref Vector128<float> component2,
in Av1ColorConversionParameters parameters);
in HeifColorConversionParameters parameters);
/// <summary>
/// Converts eight normalized AV1 samples to RGB.
@ -59,7 +59,7 @@ internal abstract partial class Av1ColorConverterBase
ref Vector256<float> component0,
ref Vector256<float> component1,
ref Vector256<float> component2,
in Av1ColorConversionParameters parameters);
in HeifColorConversionParameters parameters);
/// <summary>
/// Converts sixteen normalized AV1 samples to RGB.
@ -72,10 +72,10 @@ internal abstract partial class Av1ColorConverterBase
ref Vector512<float> component0,
ref Vector512<float> component1,
ref Vector512<float> component2,
in Av1ColorConversionParameters parameters);
in HeifColorConversionParameters parameters);
/// <summary>
/// Converts one normalized RGB sample to AV1 components.
/// Converts one normalized RGB sample to encoded components.
/// </summary>
/// <param name="red">The normalized red component.</param>
/// <param name="green">The normalized green component.</param>
@ -88,13 +88,13 @@ internal abstract partial class Av1ColorConverterBase
float red,
float green,
float blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out float component0,
out float component1,
out float component2);
/// <summary>
/// Converts four normalized RGB samples to AV1 components.
/// Converts four normalized RGB samples to encoded components.
/// </summary>
/// <param name="red">The normalized red lanes.</param>
/// <param name="green">The normalized green lanes.</param>
@ -107,13 +107,13 @@ internal abstract partial class Av1ColorConverterBase
Vector128<float> red,
Vector128<float> green,
Vector128<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector128<float> component0,
out Vector128<float> component1,
out Vector128<float> component2);
/// <summary>
/// Converts eight normalized RGB samples to AV1 components.
/// Converts eight normalized RGB samples to encoded components.
/// </summary>
/// <param name="red">The normalized red lanes.</param>
/// <param name="green">The normalized green lanes.</param>
@ -126,13 +126,13 @@ internal abstract partial class Av1ColorConverterBase
Vector256<float> red,
Vector256<float> green,
Vector256<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector256<float> component0,
out Vector256<float> component1,
out Vector256<float> component2);
/// <summary>
/// Converts sixteen normalized RGB samples to AV1 components.
/// Converts sixteen normalized RGB samples to encoded components.
/// </summary>
/// <param name="red">The normalized red lanes.</param>
/// <param name="green">The normalized green lanes.</param>
@ -145,25 +145,25 @@ internal abstract partial class Av1ColorConverterBase
Vector512<float> red,
Vector512<float> green,
Vector512<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters 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.
/// Converts an HEIF 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
internal sealed class HeifColorConverter<TOperator> : HeifColorConverterBase
where TOperator : struct, IHeifColorOperator
{
/// <summary>
/// Initializes a new instance of the <see cref="Av1ColorConverter{TOperator}"/> class.
/// Initializes a new instance of the <see cref="HeifColorConverter{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)
public HeifColorConverter(in HeifColorConversionParameters parameters, bool isMonochrome)
: base(in parameters, isMonochrome)
{
}
@ -177,7 +177,7 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
public override void ConvertToRgbInPlace(Span<float> component0, Span<float> component1, Span<float> component2)
{
Av1ColorConversionParameters parameters = this.Parameters;
HeifColorConversionParameters 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.
@ -329,7 +329,7 @@ internal abstract partial class Av1ColorConverterBase
Span<float> component2,
float maximumValue)
{
Av1ColorConversionParameters parameters = this.Parameters;
HeifColorConversionParameters 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.

192
src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.Simd.cs → src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Samples.cs

@ -7,18 +7,23 @@ using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
/// <content>
/// Provides SIMD sample widening, chroma reconstruction, planar storage, and packed output for AV1 color conversion.
/// Provides SIMD sample widening, chroma reconstruction, planar storage, and packed output for HEIF color conversion.
/// </content>
internal static partial class Av1YuvConverter
internal abstract partial class HeifColorConverterBase
{
/// <summary>
/// Defines the SIMD widening operations for one reconstructed AV1 sample type.
/// The largest value represented by a 16-bit packed RGB component.
/// </summary>
private const float UShortMaximum = ushort.MaxValue;
/// <summary>
/// Defines the SIMD widening operations for one reconstructed HEIF sample type.
/// </summary>
/// <typeparam name="TSample">The reconstructed sample type.</typeparam>
private interface ISampleLoader<TSample>
public interface IHeifSampleLoader<TSample>
where TSample : unmanaged
{
/// <summary>
@ -44,10 +49,10 @@ internal static partial class Av1YuvConverter
}
/// <summary>
/// Defines the SIMD narrowing and storage operations for one encoded AV1 sample type.
/// Defines the SIMD narrowing and storage operations for one encoded HEIF sample type.
/// </summary>
/// <typeparam name="TSample">The encoded sample type.</typeparam>
private interface ISampleStorer<TSample>
public interface IHeifSampleStorer<TSample>
where TSample : unmanaged
{
/// <summary>
@ -79,9 +84,9 @@ internal static partial class Av1YuvConverter
/// <typeparam name="TLoader">The widening operations for the sample type.</typeparam>
/// <param name="source">The reconstructed samples.</param>
/// <param name="destination">The destination component row.</param>
private static void ConvertSamplesToFloat<TSample, TLoader>(ReadOnlySpan<TSample> source, Span<float> destination)
public static void ConvertSamplesToFloat<TSample, TLoader>(ReadOnlySpan<TSample> source, Span<float> destination)
where TSample : unmanaged
where TLoader : struct, ISampleLoader<TSample>
where TLoader : struct, IHeifSampleLoader<TSample>
{
ref TSample sourceBase = ref MemoryMarshal.GetReference(source);
ref float destinationBase = ref MemoryMarshal.GetReference(destination);
@ -139,7 +144,7 @@ internal static partial class Av1YuvConverter
/// <param name="destination">The reconstructed full-width chroma row.</param>
/// <param name="scratch0">The first pooled chroma scratch row.</param>
/// <param name="scratch1">The second pooled chroma scratch row.</param>
private static void ReconstructChromaRow<TSample, TLoader>(
public static void ReconstructChromaRow<TSample, TLoader>(
ReadOnlySpan<TSample> row0,
ReadOnlySpan<TSample> row1,
int y1Weight,
@ -149,7 +154,7 @@ internal static partial class Av1YuvConverter
Span<float> scratch0,
Span<float> scratch1)
where TSample : unmanaged
where TLoader : struct, ISampleLoader<TSample>
where TLoader : struct, IHeifSampleLoader<TSample>
{
int sourceLength = subX == 0 ? destination.Length : (destination.Length + 1) >> 1;
Span<float> top = scratch0[..sourceLength];
@ -172,7 +177,7 @@ internal static partial class Av1YuvConverter
}
/// <summary>
/// Interpolates two chroma rows in place using AV1 quarter-sample weights.
/// Interpolates two chroma rows in place using quarter-sample weights.
/// </summary>
/// <param name="top">The upper row, replaced by the interpolated values.</param>
/// <param name="bottom">The lower row.</param>
@ -234,7 +239,7 @@ internal static partial class Av1YuvConverter
}
/// <summary>
/// Expands horizontally subsampled chroma to luma width using the AV1 sample-position rules.
/// Expands horizontally subsampled chroma to luma width using the selected sample-position rules.
/// </summary>
/// <param name="source">The subsampled chroma values.</param>
/// <param name="destination">The full-width chroma values.</param>
@ -249,7 +254,7 @@ internal static partial class Av1YuvConverter
if (isCentered)
{
// The first centered pair extends the left edge. Interior vectors can then read one real neighbor
// on each side and use the exact [1,3]/4 and [3,1]/4 AV1 interpolation weights.
// on each side and use the exact [1,3]/4 and [3,1]/4 interpolation weights.
StoreChromaPair(ref destinationBase, 0, source[0], ((3F * source[0]) + source[Math.Min(1, sourceLength - 1)]) * 0.25F, destination.Length);
i = 1;
}
@ -392,7 +397,7 @@ internal static partial class Av1YuvConverter
/// <param name="red">The destination red components.</param>
/// <param name="green">The destination green components.</param>
/// <param name="blue">The destination blue components.</param>
private static void DeinterleaveRgb48(ReadOnlySpan<Rgb48> source, Span<float> red, Span<float> green, Span<float> blue)
public static void DeinterleaveRgb48(ReadOnlySpan<Rgb48> source, Span<float> red, Span<float> green, Span<float> blue)
{
ref float redBase = ref MemoryMarshal.GetReference(red);
ref float greenBase = ref MemoryMarshal.GetReference(green);
@ -445,9 +450,9 @@ internal static partial class Av1YuvConverter
/// <param name="scale">The encoded range scale.</param>
/// <param name="bias">The encoded range bias.</param>
/// <param name="maximum">The largest encoded sample value.</param>
private static void WriteSamples<TSample, TStorer>(ReadOnlySpan<float> source, Span<TSample> destination, float scale, float bias, float maximum)
public static void WriteSamples<TSample, TStorer>(ReadOnlySpan<float> source, Span<TSample> destination, float scale, float bias, float maximum)
where TSample : unmanaged
where TStorer : struct, ISampleStorer<TSample>
where TStorer : struct, IHeifSampleStorer<TSample>
{
ref float sourceBase = ref MemoryMarshal.GetReference(source);
ref TSample destinationBase = ref MemoryMarshal.GetReference(destination);
@ -491,113 +496,151 @@ internal static partial class Av1YuvConverter
}
/// <summary>
/// Averages one or two planar rows into horizontally subsampled encoded samples.
/// Filters one or two planar rows into horizontally subsampled encoded samples.
/// </summary>
/// <typeparam name="TSample">The encoded sample type.</typeparam>
/// <typeparam name="TStorer">The narrowing and storage operations for the sample type.</typeparam>
/// <param name="row0">The first normalized source row.</param>
/// <param name="row1">The optional second normalized source row.</param>
/// <param name="destination">The encoded subsampled destination row.</param>
/// <param name="isCenteredX">Whether each output sample is centered between two horizontal source samples.</param>
/// <param name="row1Weight">The contribution of the second row, in the inclusive range zero through one.</param>
/// <param name="scale">The encoded range scale.</param>
/// <param name="bias">The encoded range bias.</param>
/// <param name="maximum">The largest encoded sample value.</param>
private static void WriteSubsampledSamples<TSample, TStorer>(
public static void WriteSubsampledSamples<TSample, TStorer>(
ReadOnlySpan<float> row0,
ReadOnlySpan<float> row1,
Span<TSample> destination,
bool isCenteredX,
float row1Weight,
float scale,
float bias,
float maximum)
where TSample : unmanaged
where TStorer : struct, ISampleStorer<TSample>
where TStorer : struct, IHeifSampleStorer<TSample>
{
ref float row0Base = ref MemoryMarshal.GetReference(row0);
ref float row1Base = ref MemoryMarshal.GetReference(row1);
ref TSample destinationBase = ref MemoryMarshal.GetReference(destination);
bool hasSecondRow = !row1.IsEmpty;
float averageScale = hasSecondRow ? 0.25F : 0.5F;
float effectiveRow1Weight = hasSecondRow ? row1Weight : 0F;
float row0Weight = 1F - effectiveRow1Weight;
int length = row0.Length;
int i = 0;
if (Vector512.IsHardwareAccelerated)
{
Vector512<int> evenOdd = Vector512.Create(0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15);
Vector512<float> row0WeightVector = Vector512.Create(row0Weight);
Vector512<float> row1WeightVector = Vector512.Create(effectiveRow1Weight);
int oneVectorFromEnd = length - Vector512<float>.Count;
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count)
{
Vector512<float> sum = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref row0Base, i));
Vector512<float> filtered = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref row0Base, i)) * row0WeightVector;
if (hasSecondRow)
{
sum += Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref row1Base, i));
filtered = Vector512.MultiplyAddEstimate(
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref row1Base, i)),
row1WeightVector,
filtered);
}
Vector512<float> shuffled = Vector512.Shuffle(sum, evenOdd);
Vector256<float> average = (shuffled.GetLower() + shuffled.GetUpper()) * Vector256.Create(averageScale);
TStorer.Store(ScaleBiasRoundAndClampToInt32(average, scale, bias, maximum), ref Unsafe.Add(ref destinationBase, i >> 1));
Vector512<float> shuffled = Vector512.Shuffle(filtered, evenOdd);
Vector256<float> samples = isCenteredX
? (shuffled.GetLower() + shuffled.GetUpper()) * Vector256.Create(0.5F)
: shuffled.GetLower();
TStorer.Store(ScaleBiasRoundAndClampToInt32(samples, scale, bias, maximum), ref Unsafe.Add(ref destinationBase, i >> 1));
}
}
if (Vector256.IsHardwareAccelerated)
{
Vector256<int> evenOdd = Vector256.Create(0, 2, 4, 6, 1, 3, 5, 7);
Vector256<float> row0WeightVector = Vector256.Create(row0Weight);
Vector256<float> row1WeightVector = Vector256.Create(effectiveRow1Weight);
int oneVectorFromEnd = length - Vector256<float>.Count;
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count)
{
Vector256<float> sum = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref row0Base, i));
Vector256<float> filtered = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref row0Base, i)) * row0WeightVector;
if (hasSecondRow)
{
sum += Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref row1Base, i));
filtered = Vector256.MultiplyAddEstimate(
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref row1Base, i)),
row1WeightVector,
filtered);
}
Vector256<float> shuffled = Vector256.Shuffle(sum, evenOdd);
Vector128<float> average = (shuffled.GetLower() + shuffled.GetUpper()) * Vector128.Create(averageScale);
TStorer.Store(ScaleBiasRoundAndClampToInt32(average, scale, bias, maximum), ref Unsafe.Add(ref destinationBase, i >> 1));
Vector256<float> shuffled = Vector256.Shuffle(filtered, evenOdd);
Vector128<float> samples = isCenteredX
? (shuffled.GetLower() + shuffled.GetUpper()) * Vector128.Create(0.5F)
: shuffled.GetLower();
TStorer.Store(ScaleBiasRoundAndClampToInt32(samples, scale, bias, maximum), ref Unsafe.Add(ref destinationBase, i >> 1));
}
}
if (Vector128.IsHardwareAccelerated)
{
Vector128<int> evenOdd = Vector128.Create(0, 2, 1, 3);
Vector128<float> row0WeightVector = Vector128.Create(row0Weight);
Vector128<float> row1WeightVector = Vector128.Create(effectiveRow1Weight);
int twoVectorsFromEnd = length - (Vector128<float>.Count * 2);
for (; i <= twoVectorsFromEnd; i += Vector128<float>.Count * 2)
{
Vector128<float> sum0 = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref row0Base, i));
Vector128<float> sum1 = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref row0Base, i + Vector128<float>.Count));
Vector128<float> filtered0 = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref row0Base, i)) * row0WeightVector;
Vector128<float> filtered1 = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref row0Base, i + Vector128<float>.Count)) * row0WeightVector;
if (hasSecondRow)
{
sum0 += Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref row1Base, i));
sum1 += Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref row1Base, i + Vector128<float>.Count));
filtered0 = Vector128.MultiplyAddEstimate(
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref row1Base, i)),
row1WeightVector,
filtered0);
filtered1 = Vector128.MultiplyAddEstimate(
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref row1Base, i + Vector128<float>.Count)),
row1WeightVector,
filtered1);
}
Vector128<float> shuffled0 = Vector128.Shuffle(sum0, evenOdd);
Vector128<float> shuffled1 = Vector128.Shuffle(sum1, evenOdd);
Vector64<float> average0 = (shuffled0.GetLower() + shuffled0.GetUpper()) * Vector64.Create(averageScale);
Vector64<float> average1 = (shuffled1.GetLower() + shuffled1.GetUpper()) * Vector64.Create(averageScale);
Vector128<float> average = Vector128.Create(average0, average1);
TStorer.Store(ScaleBiasRoundAndClampToInt32(average, scale, bias, maximum), ref Unsafe.Add(ref destinationBase, i >> 1));
Vector128<float> shuffled0 = Vector128.Shuffle(filtered0, evenOdd);
Vector128<float> shuffled1 = Vector128.Shuffle(filtered1, evenOdd);
Vector64<float> samples0 = isCenteredX
? (shuffled0.GetLower() + shuffled0.GetUpper()) * Vector64.Create(0.5F)
: shuffled0.GetLower();
Vector64<float> samples1 = isCenteredX
? (shuffled1.GetLower() + shuffled1.GetUpper()) * Vector64.Create(0.5F)
: shuffled1.GetLower();
TStorer.Store(
ScaleBiasRoundAndClampToInt32(Vector128.Create(samples0, samples1), scale, bias, maximum),
ref Unsafe.Add(ref destinationBase, i >> 1));
}
}
for (; i < length; i += 2)
{
int columnCount = Math.Min(2, length - i);
float sum = Unsafe.Add(ref row0Base, i);
if (columnCount == 2)
float row0Sample = Unsafe.Add(ref row0Base, i);
if (isCenteredX && i + 1 < length)
{
sum += Unsafe.Add(ref row0Base, i + 1);
row0Sample = (row0Sample + Unsafe.Add(ref row0Base, i + 1)) * 0.5F;
}
float sample = row0Sample * row0Weight;
if (hasSecondRow)
{
sum += Unsafe.Add(ref row1Base, i);
if (columnCount == 2)
float row1Sample = Unsafe.Add(ref row1Base, i);
if (isCenteredX && i + 1 < length)
{
sum += Unsafe.Add(ref row1Base, i + 1);
row1Sample = (row1Sample + Unsafe.Add(ref row1Base, i + 1)) * 0.5F;
}
sample += row1Sample * effectiveRow1Weight;
}
float sampleCount = columnCount * (hasSecondRow ? 2 : 1);
Unsafe.Add(ref destinationBase, i >> 1) = ToSample<TSample>(((sum / sampleCount) * scale) + bias, maximum);
Unsafe.Add(ref destinationBase, i >> 1) = ToSample<TSample>((sample * scale) + bias, maximum);
}
}
@ -608,7 +651,7 @@ internal static partial class Av1YuvConverter
/// <param name="green">The normalized green components.</param>
/// <param name="blue">The normalized blue components.</param>
/// <param name="destination">The destination pixels.</param>
private static void PackRgba64(ReadOnlySpan<float> red, ReadOnlySpan<float> green, ReadOnlySpan<float> blue, Span<Rgba64> destination)
public static void PackRgba64(ReadOnlySpan<float> red, ReadOnlySpan<float> green, ReadOnlySpan<float> blue, Span<Rgba64> destination)
{
ref float redBase = ref MemoryMarshal.GetReference(red);
ref float greenBase = ref MemoryMarshal.GetReference(green);
@ -667,6 +710,45 @@ internal static partial class Av1YuvConverter
}
}
/// <summary>
/// Reads an eight-bit or 16-bit unsigned sample without an intermediate conversion buffer.
/// </summary>
/// <typeparam name="TSample">The encoded sample type.</typeparam>
/// <param name="source">The source samples.</param>
/// <param name="index">The zero-based sample index.</param>
/// <returns>The sample value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GetSample<TSample>(ReadOnlySpan<TSample> source, int index)
where TSample : unmanaged
{
ref TSample sample = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), index);
return typeof(TSample) == typeof(byte)
? Unsafe.As<TSample, byte>(ref sample)
: Unsafe.As<TSample, ushort>(ref sample);
}
/// <summary>
/// Rounds and clamps a conversion result to the encoded sample range.
/// </summary>
/// <typeparam name="TSample">The encoded sample type.</typeparam>
/// <param name="value">The conversion result.</param>
/// <param name="maximum">The largest encoded sample value.</param>
/// <returns>The bounded sample.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TSample ToSample<TSample>(float value, float maximum)
where TSample : unmanaged
{
int sample = Numerics.Clamp((int)MathF.Round(value, MidpointRounding.AwayFromZero), 0, (int)maximum);
if (typeof(TSample) == typeof(byte))
{
byte result = (byte)sample;
return Unsafe.As<byte, TSample>(ref result);
}
ushort highBitDepthResult = (ushort)sample;
return Unsafe.As<ushort, TSample>(ref highBitDepthResult);
}
/// <summary>
/// Applies the encoded component range and converts four lanes to bounded integer samples.
/// </summary>
@ -775,7 +857,7 @@ internal static partial class Av1YuvConverter
/// <summary>
/// Widens reconstructed eight-bit samples using exact unsigned conversions.
/// </summary>
private readonly struct ByteSampleLoader : ISampleLoader<byte>
public readonly struct HeifByteSampleLoader : IHeifSampleLoader<byte>
{
/// <inheritdoc/>
public static Vector128<float> LoadVector128(ref byte source)
@ -808,7 +890,7 @@ internal static partial class Av1YuvConverter
/// <summary>
/// Widens reconstructed high-bit-depth samples using exact unsigned conversions.
/// </summary>
private readonly struct UShortSampleLoader : ISampleLoader<ushort>
public readonly struct HeifUShortSampleLoader : IHeifSampleLoader<ushort>
{
/// <inheritdoc/>
public static Vector128<float> LoadVector128(ref ushort source)
@ -838,7 +920,7 @@ internal static partial class Av1YuvConverter
/// <summary>
/// Narrows encoded integer lanes to eight-bit samples.
/// </summary>
private readonly struct ByteSampleStorer : ISampleStorer<byte>
public readonly struct HeifByteSampleStorer : IHeifSampleStorer<byte>
{
/// <inheritdoc/>
public static void Store(Vector128<int> source, ref byte destination)
@ -868,7 +950,7 @@ internal static partial class Av1YuvConverter
/// <summary>
/// Narrows encoded integer lanes to unsigned 16-bit samples.
/// </summary>
private readonly struct UShortSampleStorer : ISampleStorer<ushort>
public readonly struct HeifUShortSampleStorer : IHeifSampleStorer<ushort>
{
/// <inheritdoc/>
public static void Store(Vector128<int> source, ref ushort destination)

22
src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.Smpte2085Operator.cs → src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Smpte2085Operator.cs

@ -4,14 +4,14 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
internal abstract partial class Av1ColorConverterBase
internal abstract partial class HeifColorConverterBase
{
/// <summary>
/// Implements SMPTE ST 2085 YDzDx conversion for scalar and SIMD lanes.
/// </summary>
internal readonly struct Av1Smpte2085ColorOperator : IAv1ColorOperator
internal readonly struct HeifSmpte2085ColorOperator : IHeifColorOperator
{
/// <summary>
/// The SMPTE ST 2085 blue primary normalization factor.
@ -28,7 +28,7 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref float y, ref float dz, ref float dx, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref float y, ref float dz, ref float dx, in HeifColorConversionParameters parameters)
{
// The encoded YDzDx planes carry green directly. The two difference planes restore blue and red.
float g = y;
@ -41,7 +41,7 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref Vector128<float> y, ref Vector128<float> dz, ref Vector128<float> dx, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref Vector128<float> y, ref Vector128<float> dz, ref Vector128<float> dx, in HeifColorConversionParameters parameters)
{
Vector128<float> g = y;
Vector128<float> b = Vector128.MultiplyAddEstimate(Vector128.Create(2F), dz, y) / Vector128.Create(BlueNormalization);
@ -53,7 +53,7 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref Vector256<float> y, ref Vector256<float> dz, ref Vector256<float> dx, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref Vector256<float> y, ref Vector256<float> dz, ref Vector256<float> dx, in HeifColorConversionParameters parameters)
{
Vector256<float> g = y;
Vector256<float> b = Vector256.MultiplyAddEstimate(Vector256.Create(2F), dz, y) / Vector256.Create(BlueNormalization);
@ -65,7 +65,7 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref Vector512<float> y, ref Vector512<float> dz, ref Vector512<float> dx, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref Vector512<float> y, ref Vector512<float> dz, ref Vector512<float> dx, in HeifColorConversionParameters parameters)
{
Vector512<float> g = y;
Vector512<float> b = Vector512.MultiplyAddEstimate(Vector512.Create(2F), dz, y) / Vector512.Create(BlueNormalization);
@ -81,7 +81,7 @@ internal abstract partial class Av1ColorConverterBase
float r,
float g,
float b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out float y,
out float dz,
out float dx)
@ -98,7 +98,7 @@ internal abstract partial class Av1ColorConverterBase
Vector128<float> r,
Vector128<float> g,
Vector128<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector128<float> y,
out Vector128<float> dz,
out Vector128<float> dx)
@ -114,7 +114,7 @@ internal abstract partial class Av1ColorConverterBase
Vector256<float> r,
Vector256<float> g,
Vector256<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector256<float> y,
out Vector256<float> dz,
out Vector256<float> dx)
@ -130,7 +130,7 @@ internal abstract partial class Av1ColorConverterBase
Vector512<float> r,
Vector512<float> g,
Vector512<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector512<float> y,
out Vector512<float> dz,
out Vector512<float> dx)

22
src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.YCgCoOperator.cs → src/ImageSharp/Formats/Heif/Color/HeifColorConverter.YCgCoOperator.cs

@ -4,21 +4,21 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
internal abstract partial class Av1ColorConverterBase
internal abstract partial class HeifColorConverterBase
{
/// <summary>
/// Implements YCgCo conversion for scalar and SIMD lanes.
/// </summary>
internal readonly struct Av1YCgCoColorOperator : IAv1ColorOperator
internal readonly struct HeifYCgCoColorOperator : IHeifColorOperator
{
/// <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)
public static void ConvertToRgb(ref float y, ref float cg, ref float co, in HeifColorConversionParameters parameters)
{
// Reusing Y - Cg for both outer primaries keeps the inverse transform to four additions.
float temporary = y - cg;
@ -32,7 +32,7 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref Vector128<float> y, ref Vector128<float> cg, ref Vector128<float> co, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref Vector128<float> y, ref Vector128<float> cg, ref Vector128<float> co, in HeifColorConversionParameters parameters)
{
Vector128<float> temporary = y - cg;
Vector128<float> r = temporary + co;
@ -45,7 +45,7 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref Vector256<float> y, ref Vector256<float> cg, ref Vector256<float> co, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref Vector256<float> y, ref Vector256<float> cg, ref Vector256<float> co, in HeifColorConversionParameters parameters)
{
Vector256<float> temporary = y - cg;
Vector256<float> r = temporary + co;
@ -58,7 +58,7 @@ internal abstract partial class Av1ColorConverterBase
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertToRgb(ref Vector512<float> y, ref Vector512<float> cg, ref Vector512<float> co, in Av1ColorConversionParameters parameters)
public static void ConvertToRgb(ref Vector512<float> y, ref Vector512<float> cg, ref Vector512<float> co, in HeifColorConversionParameters parameters)
{
Vector512<float> temporary = y - cg;
Vector512<float> r = temporary + co;
@ -75,7 +75,7 @@ internal abstract partial class Av1ColorConverterBase
float r,
float g,
float b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out float y,
out float cg,
out float co)
@ -93,7 +93,7 @@ internal abstract partial class Av1ColorConverterBase
Vector128<float> r,
Vector128<float> g,
Vector128<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector128<float> y,
out Vector128<float> cg,
out Vector128<float> co)
@ -110,7 +110,7 @@ internal abstract partial class Av1ColorConverterBase
Vector256<float> r,
Vector256<float> g,
Vector256<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector256<float> y,
out Vector256<float> cg,
out Vector256<float> co)
@ -127,7 +127,7 @@ internal abstract partial class Av1ColorConverterBase
Vector512<float> r,
Vector512<float> g,
Vector512<float> b,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector512<float> y,
out Vector512<float> cg,
out Vector512<float> co)

42
src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.YCgCoReversibleOperator.cs → src/ImageSharp/Formats/Heif/Color/HeifColorConverter.YCgCoReversibleOperator.cs

@ -4,25 +4,25 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
internal abstract partial class Av1ColorConverterBase
internal abstract partial class HeifColorConverterBase
{
/// <summary>
/// Implements the YCgCo-Re and YCgCo-Ro integer lifting transforms for scalar and SIMD lanes.
/// </summary>
internal readonly struct Av1YCgCoReversibleColorOperator : IAv1ColorOperator
internal readonly struct HeifYCgCoReversibleColorOperator : IHeifColorOperator
{
/// <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)
public static void ConvertToRgb(ref float y, ref float cg, ref float co, in HeifColorConversionParameters parameters)
{
int yCode = (int)MathF.Floor((y * parameters.EncodedSampleMaximum) + 0.5F);
int cgCode = (int)MathF.Floor((cg * parameters.EncodedSampleMaximum) + 0.5F);
int coCode = (int)MathF.Floor((co * parameters.EncodedSampleMaximum) + 0.5F);
int yCode = (int)MathF.Floor((y * parameters.LumaSampleMaximum) + 0.5F);
int cgCode = (int)MathF.Floor((cg * parameters.LumaSampleMaximum) + 0.5F);
int coCode = (int)MathF.Floor((co * parameters.LumaSampleMaximum) + 0.5F);
// Signed arithmetic shifts are part of the reversible lifting definition. In particular, they preserve
// the specified floor division for negative odd Cg and Co values instead of truncating toward zero.
@ -42,9 +42,9 @@ internal abstract partial class Av1ColorConverterBase
ref Vector128<float> y,
ref Vector128<float> cg,
ref Vector128<float> co,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
Vector128<float> encodedMaximum = Vector128.Create(parameters.EncodedSampleMaximum);
Vector128<float> encodedMaximum = Vector128.Create(parameters.LumaSampleMaximum);
Vector128<float> half = Vector128.Create(0.5F);
Vector128<int> yCode = Vector128.ConvertToInt32(Vector128.Floor((y * encodedMaximum) + half));
Vector128<int> cgCode = Vector128.ConvertToInt32(Vector128.Floor((cg * encodedMaximum) + half));
@ -71,9 +71,9 @@ internal abstract partial class Av1ColorConverterBase
ref Vector256<float> y,
ref Vector256<float> cg,
ref Vector256<float> co,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
Vector256<float> encodedMaximum = Vector256.Create(parameters.EncodedSampleMaximum);
Vector256<float> encodedMaximum = Vector256.Create(parameters.LumaSampleMaximum);
Vector256<float> half = Vector256.Create(0.5F);
Vector256<int> yCode = Vector256.ConvertToInt32(Vector256.Floor((y * encodedMaximum) + half));
Vector256<int> cgCode = Vector256.ConvertToInt32(Vector256.Floor((cg * encodedMaximum) + half));
@ -97,9 +97,9 @@ internal abstract partial class Av1ColorConverterBase
ref Vector512<float> y,
ref Vector512<float> cg,
ref Vector512<float> co,
in Av1ColorConversionParameters parameters)
in HeifColorConversionParameters parameters)
{
Vector512<float> encodedMaximum = Vector512.Create(parameters.EncodedSampleMaximum);
Vector512<float> encodedMaximum = Vector512.Create(parameters.LumaSampleMaximum);
Vector512<float> half = Vector512.Create(0.5F);
Vector512<int> yCode = Vector512.ConvertToInt32(Vector512.Floor((y * encodedMaximum) + half));
Vector512<int> cgCode = Vector512.ConvertToInt32(Vector512.Floor((cg * encodedMaximum) + half));
@ -123,7 +123,7 @@ internal abstract partial class Av1ColorConverterBase
float red,
float green,
float blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out float y,
out float cg,
out float co)
@ -135,7 +135,7 @@ internal abstract partial class Av1ColorConverterBase
int temporary = blueCode + (coCode >> 1);
int cgCode = greenCode - temporary;
int yCode = temporary + (cgCode >> 1);
float inverseEncodedMaximum = 1F / parameters.EncodedSampleMaximum;
float inverseEncodedMaximum = 1F / parameters.LumaSampleMaximum;
y = yCode * inverseEncodedMaximum;
cg = cgCode * inverseEncodedMaximum;
co = coCode * inverseEncodedMaximum;
@ -147,7 +147,7 @@ internal abstract partial class Av1ColorConverterBase
Vector128<float> red,
Vector128<float> green,
Vector128<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector128<float> y,
out Vector128<float> cg,
out Vector128<float> co)
@ -161,7 +161,7 @@ internal abstract partial class Av1ColorConverterBase
Vector128<int> temporary = blueCode + Vector128.ShiftRightArithmetic(coCode, 1);
Vector128<int> cgCode = greenCode - temporary;
Vector128<int> yCode = temporary + Vector128.ShiftRightArithmetic(cgCode, 1);
Vector128<float> inverseEncodedMaximum = Vector128.Create(1F / parameters.EncodedSampleMaximum);
Vector128<float> inverseEncodedMaximum = Vector128.Create(1F / parameters.LumaSampleMaximum);
y = Vector128.ConvertToSingle(yCode) * inverseEncodedMaximum;
cg = Vector128.ConvertToSingle(cgCode) * inverseEncodedMaximum;
co = Vector128.ConvertToSingle(coCode) * inverseEncodedMaximum;
@ -173,7 +173,7 @@ internal abstract partial class Av1ColorConverterBase
Vector256<float> red,
Vector256<float> green,
Vector256<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector256<float> y,
out Vector256<float> cg,
out Vector256<float> co)
@ -187,7 +187,7 @@ internal abstract partial class Av1ColorConverterBase
Vector256<int> temporary = blueCode + Vector256.ShiftRightArithmetic(coCode, 1);
Vector256<int> cgCode = greenCode - temporary;
Vector256<int> yCode = temporary + Vector256.ShiftRightArithmetic(cgCode, 1);
Vector256<float> inverseEncodedMaximum = Vector256.Create(1F / parameters.EncodedSampleMaximum);
Vector256<float> inverseEncodedMaximum = Vector256.Create(1F / parameters.LumaSampleMaximum);
y = Vector256.ConvertToSingle(yCode) * inverseEncodedMaximum;
cg = Vector256.ConvertToSingle(cgCode) * inverseEncodedMaximum;
co = Vector256.ConvertToSingle(coCode) * inverseEncodedMaximum;
@ -199,7 +199,7 @@ internal abstract partial class Av1ColorConverterBase
Vector512<float> red,
Vector512<float> green,
Vector512<float> blue,
in Av1ColorConversionParameters parameters,
in HeifColorConversionParameters parameters,
out Vector512<float> y,
out Vector512<float> cg,
out Vector512<float> co)
@ -213,7 +213,7 @@ internal abstract partial class Av1ColorConverterBase
Vector512<int> temporary = blueCode + Vector512.ShiftRightArithmetic(coCode, 1);
Vector512<int> cgCode = greenCode - temporary;
Vector512<int> yCode = temporary + Vector512.ShiftRightArithmetic(cgCode, 1);
Vector512<float> inverseEncodedMaximum = Vector512.Create(1F / parameters.EncodedSampleMaximum);
Vector512<float> inverseEncodedMaximum = Vector512.Create(1F / parameters.LumaSampleMaximum);
y = Vector512.ConvertToSingle(yCode) * inverseEncodedMaximum;
cg = Vector512.ConvertToSingle(cgCode) * inverseEncodedMaximum;
co = Vector512.ConvertToSingle(coCode) * inverseEncodedMaximum;

36
src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.cs → src/ImageSharp/Formats/Heif/Color/HeifColorConverter.cs

@ -1,12 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
/// <summary>
/// Identifies the H.273 matrix operation used between encoded planes and RGB components.
/// </summary>
internal enum Av1ColorConversionMode
internal enum HeifColorConversionMode
{
/// <summary>
/// A coefficient-based YCbCr matrix conversion.
@ -50,16 +50,16 @@ internal enum Av1ColorConversionMode
}
/// <summary>
/// Converts normalized component planes between an AV1 color model and RGB.
/// Converts normalized component planes between an encoded HEIF color model and RGB.
/// </summary>
internal abstract partial class Av1ColorConverterBase
internal abstract partial class HeifColorConverterBase
{
/// <summary>
/// Initializes a new instance of the <see cref="Av1ColorConverterBase"/> class.
/// Initializes a new instance of the <see cref="HeifColorConverterBase"/> 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)
protected HeifColorConverterBase(in HeifColorConversionParameters parameters, bool isMonochrome)
{
this.Parameters = parameters;
this.IsMonochrome = isMonochrome;
@ -68,7 +68,7 @@ internal abstract partial class Av1ColorConverterBase
/// <summary>
/// Gets the resolved H.273 conversion parameters.
/// </summary>
protected Av1ColorConversionParameters Parameters { get; }
protected HeifColorConversionParameters Parameters { get; }
/// <summary>
/// Gets a value indicating whether the frame contains only luma samples.
@ -96,7 +96,7 @@ internal abstract partial class Av1ColorConverterBase
public abstract float ChromaBias { get; }
/// <summary>
/// Converts normalized AV1 components to normalized RGB in place.
/// Converts normalized encoded 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>
@ -104,7 +104,7 @@ internal abstract partial class Av1ColorConverterBase
public abstract void ConvertToRgbInPlace(Span<float> component0, Span<float> component1, Span<float> component2);
/// <summary>
/// Converts normalized RGB components to normalized AV1 components in place.
/// Converts normalized RGB components to normalized encoded 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>
@ -123,16 +123,16 @@ internal abstract partial class Av1ColorConverterBase
/// <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)
public static HeifColorConverterBase Create(HeifColorConversionMode mode, in HeifColorConversionParameters 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),
Av1ColorConversionMode.IptC2 => new Av1ColorConverter<Av1IptC2ColorOperator>(in parameters, isMonochrome),
Av1ColorConversionMode.YCgCoReversible => new Av1ColorConverter<Av1YCgCoReversibleColorOperator>(in parameters, isMonochrome),
_ => new Av1ColorConverter<Av1CoefficientColorOperator>(in parameters, isMonochrome),
HeifColorConversionMode.Identity => new HeifColorConverter<HeifIdentityColorOperator>(in parameters, isMonochrome),
HeifColorConversionMode.YCgCo => new HeifColorConverter<HeifYCgCoColorOperator>(in parameters, isMonochrome),
HeifColorConversionMode.Smpte2085 => new HeifColorConverter<HeifSmpte2085ColorOperator>(in parameters, isMonochrome),
HeifColorConversionMode.ConstantLuminance => new HeifColorConverter<HeifConstantLuminanceColorOperator>(in parameters, isMonochrome),
HeifColorConversionMode.ICtCp => new HeifColorConverter<HeifICtCpColorOperator>(in parameters, isMonochrome),
HeifColorConversionMode.IptC2 => new HeifColorConverter<HeifIptC2ColorOperator>(in parameters, isMonochrome),
HeifColorConversionMode.YCgCoReversible => new HeifColorConverter<HeifYCgCoReversibleColorOperator>(in parameters, isMonochrome),
_ => new HeifColorConverter<HeifCoefficientColorOperator>(in parameters, isMonochrome),
};
}

92
src/ImageSharp/Formats/Heif/Av1/Av1TransferFunctions.Simd.cs → src/ImageSharp/Formats/Heif/Color/HeifTransferFunctions.Simd.cs

@ -2,15 +2,15 @@
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using static SixLabors.ImageSharp.Formats.Heif.Color.HeifTransferVectorOperators;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
/// <content>
/// Provides the SIMD implementations of the H.273 transfer characteristics used by AV1 color conversion.
/// Provides the SIMD implementations of the H.273 transfer characteristics used by HEIF color conversion.
/// </content>
internal static partial class Av1TransferFunctions
internal static partial class HeifTransferFunctions
{
/// <summary>
/// Converts four nonlinear signal values to their H.273 linear-domain values.
@ -18,7 +18,7 @@ internal static partial class Av1TransferFunctions
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
/// <param name="value">The nonlinear signal values.</param>
/// <returns>The corresponding linear-domain values.</returns>
public static Vector128<float> ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector128<float> value)
public static Vector128<float> ToLinear(CicpTransferCharacteristics transferCharacteristics, Vector128<float> value)
=> ToLinear<Vector128<float>, Vector128Operator>(transferCharacteristics, value);
/// <summary>
@ -27,7 +27,7 @@ internal static partial class Av1TransferFunctions
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
/// <param name="value">The nonlinear signal values.</param>
/// <returns>The corresponding linear-domain values.</returns>
public static Vector256<float> ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector256<float> value)
public static Vector256<float> ToLinear(CicpTransferCharacteristics transferCharacteristics, Vector256<float> value)
=> ToLinear<Vector256<float>, Vector256Operator>(transferCharacteristics, value);
/// <summary>
@ -36,7 +36,7 @@ internal static partial class Av1TransferFunctions
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
/// <param name="value">The nonlinear signal values.</param>
/// <returns>The corresponding linear-domain values.</returns>
public static Vector512<float> ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector512<float> value)
public static Vector512<float> ToLinear(CicpTransferCharacteristics transferCharacteristics, Vector512<float> value)
=> ToLinear<Vector512<float>, Vector512Operator>(transferCharacteristics, value);
/// <summary>
@ -45,7 +45,7 @@ internal static partial class Av1TransferFunctions
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
/// <param name="value">The linear signal values.</param>
/// <returns>The corresponding nonlinear-domain values.</returns>
public static Vector128<float> ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector128<float> value)
public static Vector128<float> ToGamma(CicpTransferCharacteristics transferCharacteristics, Vector128<float> value)
=> ToGamma<Vector128<float>, Vector128Operator>(transferCharacteristics, value);
/// <summary>
@ -54,7 +54,7 @@ internal static partial class Av1TransferFunctions
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
/// <param name="value">The linear signal values.</param>
/// <returns>The corresponding nonlinear-domain values.</returns>
public static Vector256<float> ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector256<float> value)
public static Vector256<float> ToGamma(CicpTransferCharacteristics transferCharacteristics, Vector256<float> value)
=> ToGamma<Vector256<float>, Vector256Operator>(transferCharacteristics, value);
/// <summary>
@ -63,7 +63,7 @@ internal static partial class Av1TransferFunctions
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
/// <param name="value">The linear signal values.</param>
/// <returns>The corresponding nonlinear-domain values.</returns>
public static Vector512<float> ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector512<float> value)
public static Vector512<float> ToGamma(CicpTransferCharacteristics transferCharacteristics, Vector512<float> value)
=> ToGamma<Vector512<float>, Vector512Operator>(transferCharacteristics, value);
/// <summary>
@ -74,7 +74,7 @@ internal static partial class Av1TransferFunctions
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
/// <param name="value">The nonlinear signal values.</param>
/// <returns>The corresponding linear-domain values.</returns>
private static TVector ToLinear<TVector, TOperator>(ObuTransferCharacteristics transferCharacteristics, TVector value)
private static TVector ToLinear<TVector, TOperator>(CicpTransferCharacteristics transferCharacteristics, TVector value)
where TVector : struct
where TOperator : struct, ITransferVectorOperator<TVector>
{
@ -83,20 +83,20 @@ internal static partial class Av1TransferFunctions
switch (transferCharacteristics)
{
case ObuTransferCharacteristics.Bt709:
case ObuTransferCharacteristics.Bt601:
case ObuTransferCharacteristics.Bt202010Bit:
case ObuTransferCharacteristics.Bt202012Bit:
case CicpTransferCharacteristics.ItuRBt709_6:
case CicpTransferCharacteristics.ItuRBt601_7:
case CicpTransferCharacteristics.ItuRBt2020_2_10bit:
case CicpTransferCharacteristics.ItuRBt2020_2_12bit:
return ToLinearBt709<TVector, TOperator>(value);
case ObuTransferCharacteristics.Bt470M:
case CicpTransferCharacteristics.Gamma2_2:
return Power<TVector, TOperator>(TOperator.Min(TOperator.Max(value, zero), one), 2.2F);
case ObuTransferCharacteristics.Bt470BG:
case CicpTransferCharacteristics.Gamma2_8:
return Power<TVector, TOperator>(TOperator.Min(TOperator.Max(value, zero), one), 2.8F);
case ObuTransferCharacteristics.Smpte240:
case CicpTransferCharacteristics.SmpteSt240:
return ToLinearSmpte240<TVector, TOperator>(value);
case ObuTransferCharacteristics.Linear:
case CicpTransferCharacteristics.Linear:
return TOperator.Min(TOperator.Max(value, zero), one);
case ObuTransferCharacteristics.Log100:
case CicpTransferCharacteristics.Log100:
{
// H.273 assigns an interval to zero for logarithmic curves. The scalar midpoint convention is
// selected lane-wise after evaluating the positive branch, which keeps the hot path branchless.
@ -105,24 +105,24 @@ internal static partial class Av1TransferFunctions
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, zero), TOperator.Create(0.005F), positive);
}
case ObuTransferCharacteristics.Log100Sqrt10:
case CicpTransferCharacteristics.Log100Sqrt:
{
TVector exponent = TOperator.Multiply(TOperator.Subtract(TOperator.Min(value, one), one), TOperator.Create(2.5F * 2.302585092994046F));
TVector positive = TOperator.Exp(exponent);
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, zero), TOperator.Create(0.00158113883F), positive);
}
case ObuTransferCharacteristics.Iec61966:
case CicpTransferCharacteristics.Iec61966_2_4:
return ToLinearIec61966<TVector, TOperator>(value);
case ObuTransferCharacteristics.Bt1361:
case CicpTransferCharacteristics.ItuRBt1361_0:
return ToLinearBt1361<TVector, TOperator>(value);
case ObuTransferCharacteristics.Srgb:
case CicpTransferCharacteristics.Iec61966_2_1:
return ToLinearSrgb<TVector, TOperator>(value);
case ObuTransferCharacteristics.Smpte2084:
case CicpTransferCharacteristics.SmpteSt2084:
return ToLinearPq<TVector, TOperator>(value);
case ObuTransferCharacteristics.Smpte428:
case CicpTransferCharacteristics.SmpteSt428_1:
return TOperator.Divide(Power<TVector, TOperator>(TOperator.Max(value, zero), 2.6F), TOperator.Create(Smpte428Scale));
case ObuTransferCharacteristics.Hlg:
case CicpTransferCharacteristics.AribStdB67:
return ToLinearHlg<TVector, TOperator>(value);
default:
return ToLinearBt709<TVector, TOperator>(value);
@ -137,7 +137,7 @@ internal static partial class Av1TransferFunctions
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
/// <param name="value">The linear signal values.</param>
/// <returns>The corresponding nonlinear-domain values.</returns>
private static TVector ToGamma<TVector, TOperator>(ObuTransferCharacteristics transferCharacteristics, TVector value)
private static TVector ToGamma<TVector, TOperator>(CicpTransferCharacteristics transferCharacteristics, TVector value)
where TVector : struct
where TOperator : struct, ITransferVectorOperator<TVector>
{
@ -146,20 +146,20 @@ internal static partial class Av1TransferFunctions
switch (transferCharacteristics)
{
case ObuTransferCharacteristics.Bt709:
case ObuTransferCharacteristics.Bt601:
case ObuTransferCharacteristics.Bt202010Bit:
case ObuTransferCharacteristics.Bt202012Bit:
case CicpTransferCharacteristics.ItuRBt709_6:
case CicpTransferCharacteristics.ItuRBt601_7:
case CicpTransferCharacteristics.ItuRBt2020_2_10bit:
case CicpTransferCharacteristics.ItuRBt2020_2_12bit:
return ToGammaBt709<TVector, TOperator>(value);
case ObuTransferCharacteristics.Bt470M:
case CicpTransferCharacteristics.Gamma2_2:
return Power<TVector, TOperator>(TOperator.Min(TOperator.Max(value, zero), one), 1F / 2.2F);
case ObuTransferCharacteristics.Bt470BG:
case CicpTransferCharacteristics.Gamma2_8:
return Power<TVector, TOperator>(TOperator.Min(TOperator.Max(value, zero), one), 1F / 2.8F);
case ObuTransferCharacteristics.Smpte240:
case CicpTransferCharacteristics.SmpteSt240:
return ToGammaSmpte240<TVector, TOperator>(value);
case ObuTransferCharacteristics.Linear:
case CicpTransferCharacteristics.Linear:
return TOperator.Min(TOperator.Max(value, zero), one);
case ObuTransferCharacteristics.Log100:
case CicpTransferCharacteristics.Log100:
{
// Clamp inactive lanes to the threshold before Log. ConditionalSelect does not short-circuit,
// so this prevents negative input lanes from contaminating the vector operation with NaN values.
@ -169,7 +169,7 @@ internal static partial class Av1TransferFunctions
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, threshold), zero, positive);
}
case ObuTransferCharacteristics.Log100Sqrt10:
case CicpTransferCharacteristics.Log100Sqrt:
{
TVector threshold = TOperator.Create(0.00316227766F);
TVector bounded = TOperator.Min(TOperator.Max(value, threshold), one);
@ -177,17 +177,17 @@ internal static partial class Av1TransferFunctions
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, threshold), zero, positive);
}
case ObuTransferCharacteristics.Iec61966:
case CicpTransferCharacteristics.Iec61966_2_4:
return ToGammaIec61966<TVector, TOperator>(value);
case ObuTransferCharacteristics.Bt1361:
case CicpTransferCharacteristics.ItuRBt1361_0:
return ToGammaBt1361<TVector, TOperator>(value);
case ObuTransferCharacteristics.Srgb:
case CicpTransferCharacteristics.Iec61966_2_1:
return ToGammaSrgb<TVector, TOperator>(value);
case ObuTransferCharacteristics.Smpte2084:
case CicpTransferCharacteristics.SmpteSt2084:
return ToGammaPq<TVector, TOperator>(value);
case ObuTransferCharacteristics.Smpte428:
case CicpTransferCharacteristics.SmpteSt428_1:
return Power<TVector, TOperator>(TOperator.Multiply(TOperator.Create(Smpte428Scale), TOperator.Max(value, zero)), 1F / 2.6F);
case ObuTransferCharacteristics.Hlg:
case CicpTransferCharacteristics.AribStdB67:
return ToGammaHlg<TVector, TOperator>(value);
default:
return ToGammaBt709<TVector, TOperator>(value);
@ -516,7 +516,7 @@ internal static partial class Av1TransferFunctions
/// <summary>
/// Contains the stateless vector-width operators used by the shared H.273 transfer-function formulas.
/// </summary>
internal static class Av1TransferVectorOperators
internal static class HeifTransferVectorOperators
{
/// <summary>
/// Defines the lane-wise operations required by the shared H.273 SIMD formulas.

76
src/ImageSharp/Formats/Heif/Av1/Av1TransferFunctions.cs → src/ImageSharp/Formats/Heif/Color/HeifTransferFunctions.cs

@ -1,14 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Formats.Heif.Color;
/// <summary>
/// Applies the H.273 transfer characteristics used by AV1 color conversion.
/// Applies the H.273 transfer characteristics used by HEIF color conversion.
/// </summary>
internal static partial class Av1TransferFunctions
internal static partial class HeifTransferFunctions
{
/// <summary>
/// The BT.709 and BT.2020 nonlinear scale factor.
@ -91,40 +91,40 @@ internal static partial class Av1TransferFunctions
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
/// <param name="value">The nonlinear signal value.</param>
/// <returns>The corresponding linear-domain value.</returns>
public static float ToLinear(ObuTransferCharacteristics transferCharacteristics, float value)
public static float ToLinear(CicpTransferCharacteristics transferCharacteristics, float value)
{
switch (transferCharacteristics)
{
case ObuTransferCharacteristics.Bt709:
case ObuTransferCharacteristics.Bt601:
case ObuTransferCharacteristics.Bt202010Bit:
case ObuTransferCharacteristics.Bt202012Bit:
case CicpTransferCharacteristics.ItuRBt709_6:
case CicpTransferCharacteristics.ItuRBt601_7:
case CicpTransferCharacteristics.ItuRBt2020_2_10bit:
case CicpTransferCharacteristics.ItuRBt2020_2_12bit:
return ToLinearBt709(value);
case ObuTransferCharacteristics.Bt470M:
case CicpTransferCharacteristics.Gamma2_2:
return MathF.Pow(Math.Clamp(value, 0F, 1F), 2.2F);
case ObuTransferCharacteristics.Bt470BG:
case CicpTransferCharacteristics.Gamma2_8:
return MathF.Pow(Math.Clamp(value, 0F, 1F), 2.8F);
case ObuTransferCharacteristics.Smpte240:
case CicpTransferCharacteristics.SmpteSt240:
return ToLinearSmpte240(value);
case ObuTransferCharacteristics.Linear:
case CicpTransferCharacteristics.Linear:
return Math.Clamp(value, 0F, 1F);
case ObuTransferCharacteristics.Log100:
case CicpTransferCharacteristics.Log100:
// Zero represents an interval rather than one linear value. The midpoint matches libavif and
// minimizes the worst-case round-trip error when constant-luminance content is decoded.
return value <= 0F ? 0.005F : MathF.Pow(10F, 2F * (MathF.Min(value, 1F) - 1F));
case ObuTransferCharacteristics.Log100Sqrt10:
case CicpTransferCharacteristics.Log100Sqrt:
return value <= 0F ? 0.00158113883F : MathF.Pow(10F, 2.5F * (MathF.Min(value, 1F) - 1F));
case ObuTransferCharacteristics.Iec61966:
case CicpTransferCharacteristics.Iec61966_2_4:
return ToLinearIec61966(value);
case ObuTransferCharacteristics.Bt1361:
case CicpTransferCharacteristics.ItuRBt1361_0:
return ToLinearBt1361(value);
case ObuTransferCharacteristics.Srgb:
case CicpTransferCharacteristics.Iec61966_2_1:
return ToLinearSrgb(value);
case ObuTransferCharacteristics.Smpte2084:
case CicpTransferCharacteristics.SmpteSt2084:
return ToLinearPq(value);
case ObuTransferCharacteristics.Smpte428:
case CicpTransferCharacteristics.SmpteSt428_1:
return MathF.Pow(MathF.Max(value, 0F), 2.6F) / Smpte428Scale;
case ObuTransferCharacteristics.Hlg:
case CicpTransferCharacteristics.AribStdB67:
return ToLinearHlg(value);
default:
// H.273 leaves unspecified and reserved transfer values to the application. Match libavif's
@ -139,38 +139,38 @@ internal static partial class Av1TransferFunctions
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
/// <param name="value">The linear signal value.</param>
/// <returns>The corresponding nonlinear-domain value.</returns>
public static float ToGamma(ObuTransferCharacteristics transferCharacteristics, float value)
public static float ToGamma(CicpTransferCharacteristics transferCharacteristics, float value)
{
switch (transferCharacteristics)
{
case ObuTransferCharacteristics.Bt709:
case ObuTransferCharacteristics.Bt601:
case ObuTransferCharacteristics.Bt202010Bit:
case ObuTransferCharacteristics.Bt202012Bit:
case CicpTransferCharacteristics.ItuRBt709_6:
case CicpTransferCharacteristics.ItuRBt601_7:
case CicpTransferCharacteristics.ItuRBt2020_2_10bit:
case CicpTransferCharacteristics.ItuRBt2020_2_12bit:
return ToGammaBt709(value);
case ObuTransferCharacteristics.Bt470M:
case CicpTransferCharacteristics.Gamma2_2:
return MathF.Pow(Math.Clamp(value, 0F, 1F), 1F / 2.2F);
case ObuTransferCharacteristics.Bt470BG:
case CicpTransferCharacteristics.Gamma2_8:
return MathF.Pow(Math.Clamp(value, 0F, 1F), 1F / 2.8F);
case ObuTransferCharacteristics.Smpte240:
case CicpTransferCharacteristics.SmpteSt240:
return ToGammaSmpte240(value);
case ObuTransferCharacteristics.Linear:
case CicpTransferCharacteristics.Linear:
return Math.Clamp(value, 0F, 1F);
case ObuTransferCharacteristics.Log100:
case CicpTransferCharacteristics.Log100:
return value <= 0.01F ? 0F : 1F + (MathF.Log10(MathF.Min(value, 1F)) / 2F);
case ObuTransferCharacteristics.Log100Sqrt10:
case CicpTransferCharacteristics.Log100Sqrt:
return value <= 0.00316227766F ? 0F : 1F + (MathF.Log10(MathF.Min(value, 1F)) / 2.5F);
case ObuTransferCharacteristics.Iec61966:
case CicpTransferCharacteristics.Iec61966_2_4:
return ToGammaIec61966(value);
case ObuTransferCharacteristics.Bt1361:
case CicpTransferCharacteristics.ItuRBt1361_0:
return ToGammaBt1361(value);
case ObuTransferCharacteristics.Srgb:
case CicpTransferCharacteristics.Iec61966_2_1:
return ToGammaSrgb(value);
case ObuTransferCharacteristics.Smpte2084:
case CicpTransferCharacteristics.SmpteSt2084:
return ToGammaPq(value);
case ObuTransferCharacteristics.Smpte428:
case CicpTransferCharacteristics.SmpteSt428_1:
return MathF.Pow(Smpte428Scale * MathF.Max(value, 0F), 1F / 2.6F);
case ObuTransferCharacteristics.Hlg:
case CicpTransferCharacteristics.AribStdB67:
return ToGammaHlg(value);
default:
return ToGammaBt709(value);

40
src/ImageSharp/Formats/Heif/Hevc/HevcChromaSampleLocation.cs

@ -0,0 +1,40 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
/// <summary>
/// Identifies the location of a 4:2:0 chroma sample relative to its associated two-by-two luma sample region.
/// </summary>
internal enum HevcChromaSampleLocation : byte
{
/// <summary>
/// The chroma sample is horizontally co-sited with the left luma column and vertically centered.
/// </summary>
Left = 0,
/// <summary>
/// The chroma sample is horizontally and vertically centered.
/// </summary>
Center = 1,
/// <summary>
/// The chroma sample is co-sited with the top-left luma sample.
/// </summary>
TopLeft = 2,
/// <summary>
/// The chroma sample is horizontally centered and co-sited with the top luma row.
/// </summary>
Top = 3,
/// <summary>
/// The chroma sample is horizontally co-sited with the left luma column and vertically co-sited with the bottom luma row.
/// </summary>
BottomLeft = 4,
/// <summary>
/// The chroma sample is horizontally centered and vertically co-sited with the bottom luma row.
/// </summary>
Bottom = 5,
}

41
src/ImageSharp/Formats/Heif/Hevc/HevcPictureBuffer.cs

@ -26,13 +26,42 @@ internal sealed class HevcPictureBuffer : IDisposable
/// <param name="configuration">The configuration providing the image memory allocator.</param>
/// <param name="sequenceParameterSet">The coded dimensions, precision, and chroma layout.</param>
public HevcPictureBuffer(Configuration configuration, HevcSequenceParameterSet sequenceParameterSet)
: this(
configuration,
sequenceParameterSet.Width,
sequenceParameterSet.Height,
sequenceParameterSet.BitDepthLuma,
sequenceParameterSet.BitDepthChroma,
sequenceParameterSet.ChromaFormat,
sequenceParameterSet.SeparateColorPlaneFlag)
{
this.Width = sequenceParameterSet.Width;
this.Height = sequenceParameterSet.Height;
this.BitDepthLuma = sequenceParameterSet.BitDepthLuma;
this.BitDepthChroma = sequenceParameterSet.BitDepthChroma;
this.ChromaFormat = sequenceParameterSet.ChromaFormat;
this.SeparateColorPlane = sequenceParameterSet.SeparateColorPlaneFlag;
}
/// <summary>
/// Initializes a new instance of the <see cref="HevcPictureBuffer"/> class for encoder-owned component planes.
/// </summary>
/// <param name="configuration">The configuration providing the image memory allocator.</param>
/// <param name="width">The coded luma width.</param>
/// <param name="height">The coded luma height.</param>
/// <param name="bitDepthLuma">The luma sample precision.</param>
/// <param name="bitDepthChroma">The chroma sample precision.</param>
/// <param name="chromaFormat">The HEVC chroma-format identifier.</param>
/// <param name="separateColorPlane">Whether 4:4:4 components are coded as separate color planes.</param>
public HevcPictureBuffer(
Configuration configuration,
int width,
int height,
int bitDepthLuma,
int bitDepthChroma,
byte chromaFormat,
bool separateColorPlane)
{
this.Width = width;
this.Height = height;
this.BitDepthLuma = bitDepthLuma;
this.BitDepthChroma = bitDepthChroma;
this.ChromaFormat = chromaFormat;
this.SeparateColorPlane = separateColorPlane;
// Separate color planes are independently coded at full resolution even though chroma_format_idc is 4:4:4.
this.chromaSubsamplingX = !this.SeparateColorPlane && this.ChromaFormat is 1 or 2 ? 1 : 0;

8
src/ImageSharp/Formats/Heif/Hevc/HevcVideoUsabilityInformation.cs

@ -70,8 +70,8 @@ internal sealed class HevcVideoUsabilityInformation
throw new InvalidImageContentException("The HEVC VUI declares an invalid chroma sample location.");
}
this.ChromaSampleLocationTopField = (byte)topFieldLocation;
this.ChromaSampleLocationBottomField = (byte)bottomFieldLocation;
this.ChromaSampleLocationTopField = (HevcChromaSampleLocation)topFieldLocation;
this.ChromaSampleLocationBottomField = (HevcChromaSampleLocation)bottomFieldLocation;
}
reader.ReadFlag();
@ -195,12 +195,12 @@ internal sealed class HevcVideoUsabilityInformation
/// <summary>
/// Gets the top-field chroma sample-location identifier.
/// </summary>
public byte ChromaSampleLocationTopField { get; }
public HevcChromaSampleLocation ChromaSampleLocationTopField { get; }
/// <summary>
/// Gets the bottom-field chroma sample-location identifier.
/// </summary>
public byte ChromaSampleLocationBottomField { get; }
public HevcChromaSampleLocation ChromaSampleLocationBottomField { get; }
/// <summary>
/// Gets a value indicating whether a default display window is present.

592
src/ImageSharp/Formats/Heif/Hevc/HevcYuvConverter.cs

@ -0,0 +1,592 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Heif.Color;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.PixelFormats;
using static SixLabors.ImageSharp.Formats.Heif.Color.HeifColorConverterBase;
namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
/// <summary>
/// Converts between reconstructed HEVC component planes and packed ImageSharp pixels.
/// </summary>
internal static class HevcYuvConverter
{
/// <summary>
/// The largest value represented by an eight-bit packed RGB component.
/// </summary>
private const float ByteMaximum = byte.MaxValue;
/// <summary>
/// The largest value represented by a 16-bit packed RGB component.
/// </summary>
private const float UShortMaximum = ushort.MaxValue;
/// <summary>
/// Gets the horizontal offset in half-luma-sample units for each HEVC 4:2:0 chroma-location code.
/// </summary>
private static ReadOnlySpan<byte> ChromaLocationX => [0, 1, 0, 1, 0, 1];
/// <summary>
/// Gets the vertical offset in half-luma-sample units for each HEVC 4:2:0 chroma-location code.
/// </summary>
private static ReadOnlySpan<byte> ChromaLocationY => [1, 1, 0, 0, 2, 2];
/// <summary>
/// Converts reconstructed HEVC component planes to packed pixels.
/// </summary>
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
/// <param name="configuration">The configuration used for allocation and pixel conversion.</param>
/// <param name="picture">The reconstructed HEVC picture.</param>
/// <param name="image">The destination image frame.</param>
/// <param name="colorProfile">The effective H.273 color description.</param>
/// <param name="chromaSampleLocation">The progressive-frame 4:2:0 chroma sample location.</param>
public static void ConvertToRgb<TPixel>(
Configuration configuration,
HevcPictureBuffer picture,
ImageFrame<TPixel> image,
CicpProfile colorProfile,
HevcChromaSampleLocation chromaSampleLocation)
where TPixel : unmanaged, IPixel<TPixel>
{
HeifColorConversionParameters parameters = GetConversionParameters(picture, colorProfile, out HeifColorConversionMode mode);
HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, picture.ChromaFormat == 0);
YuvToRgbRowConverter<TPixel> converter = new(configuration, picture, image, colorConverter, chromaSampleLocation);
using IMemoryOwner<float> scratchOwner = configuration.MemoryAllocator.Allocate<float>(converter.BufferLength);
Span<float> scratch = scratchOwner.GetSpan();
if (converter.UsesBytePacking)
{
using IMemoryOwner<TPixel> proxyOwner = configuration.MemoryAllocator.Allocate<TPixel>(image.Width + 3);
Span<TPixel> proxy = proxyOwner.GetSpan()[..(image.Width + 3)];
for (int y = 0; y < image.Height; y++)
{
converter.Convert(y, scratch, proxy);
}
return;
}
for (int y = 0; y < image.Height; y++)
{
converter.Convert(y, scratch, Span<TPixel>.Empty);
}
}
/// <summary>
/// Converts packed pixels to the configured HEVC component planes.
/// </summary>
/// <typeparam name="TPixel">The source pixel type.</typeparam>
/// <param name="configuration">The configuration used for allocation and pixel conversion.</param>
/// <param name="image">The source image frame.</param>
/// <param name="picture">The destination HEVC picture.</param>
/// <param name="colorProfile">The H.273 color description to encode.</param>
/// <param name="chromaSampleLocation">The progressive-frame 4:2:0 chroma sample location.</param>
public static void ConvertFromRgb<TPixel>(
Configuration configuration,
ImageFrame<TPixel> image,
HevcPictureBuffer picture,
CicpProfile colorProfile,
HevcChromaSampleLocation chromaSampleLocation)
where TPixel : unmanaged, IPixel<TPixel>
{
HeifColorConversionParameters parameters = GetConversionParameters(picture, colorProfile, out HeifColorConversionMode mode);
HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, picture.ChromaFormat == 0);
RgbToYuvRowConverter<TPixel> converter = new(configuration, picture, image, colorConverter, chromaSampleLocation, in parameters);
using IMemoryOwner<float> componentOwner = configuration.MemoryAllocator.Allocate<float>(converter.ComponentBufferLength);
Span<float> components = componentOwner.GetSpan();
if (converter.UsesByteInput)
{
converter.Convert(Span<Rgb48>.Empty, components);
return;
}
using IMemoryOwner<Rgb48> packedOwner = configuration.MemoryAllocator.Allocate<Rgb48>(image.Width);
converter.Convert(packedOwner.GetSpan()[..image.Width], components);
}
/// <summary>
/// Resolves the shared H.273 conversion parameters for an HEVC picture.
/// </summary>
/// <param name="picture">The picture defining component precision and sampling.</param>
/// <param name="colorProfile">The effective H.273 color description.</param>
/// <param name="mode">The resolved color conversion operation.</param>
/// <returns>The immutable scalar and SIMD conversion parameters.</returns>
private static HeifColorConversionParameters GetConversionParameters(
HevcPictureBuffer picture,
CicpProfile colorProfile,
out HeifColorConversionMode mode)
=> HeifColorConversionParameters.Create(
colorProfile.ColorPrimaries,
colorProfile.TransferCharacteristics,
colorProfile.MatrixCoefficients,
colorProfile.FullRange,
picture.BitDepthLuma,
picture.ChromaFormat == 0 ? picture.BitDepthLuma : picture.BitDepthChroma,
picture.ChromaFormat == 0,
picture.ChromaFormat == 3,
out mode);
/// <summary>
/// Resolves the two chroma rows and quarter-sample weight surrounding a luma row.
/// </summary>
/// <param name="coordinate">The luma row coordinate.</param>
/// <param name="subsampling">The vertical chroma subsampling shift.</param>
/// <param name="position">The chroma offset in half-luma-sample units.</param>
/// <param name="maximum">The last available chroma row.</param>
/// <param name="lower">The lower chroma row.</param>
/// <param name="upper">The upper chroma row.</param>
/// <param name="upperWeight">The upper-row weight with a denominator of four.</param>
private static void GetChromaCoordinates(
int coordinate,
int subsampling,
int position,
int maximum,
out int lower,
out int upper,
out int upperWeight)
{
if (subsampling == 0)
{
lower = coordinate;
upper = coordinate;
upperWeight = 0;
return;
}
// Multiplying the source coordinate by four keeps all six HEVC 4:2:0 locations integral. Floor division
// is required above the top edge because the first centered sample lies after the first luma row.
int quarterCoordinate = (coordinate * 2) - position;
int unclampedLower = quarterCoordinate >= 0 ? quarterCoordinate >> 2 : -((-quarterCoordinate + 3) >> 2);
int fraction = quarterCoordinate - (unclampedLower * 4);
lower = Numerics.Clamp(unclampedLower, 0, maximum);
upper = Numerics.Clamp(unclampedLower + 1, 0, maximum);
upperWeight = lower == upper ? 0 : fraction;
}
/// <summary>
/// Resolves the luma-relative chroma offsets for the selected plane layout.
/// </summary>
/// <param name="picture">The HEVC picture layout.</param>
/// <param name="chromaSampleLocation">The signaled 4:2:0 location.</param>
/// <param name="horizontalPosition">The horizontal offset in half-luma-sample units.</param>
/// <param name="verticalPosition">The vertical offset in half-luma-sample units.</param>
private static void GetChromaPosition(
HevcPictureBuffer picture,
HevcChromaSampleLocation chromaSampleLocation,
out int horizontalPosition,
out int verticalPosition)
{
if (picture.ChromaFormat != 1 || picture.SeparateColorPlane)
{
horizontalPosition = 0;
verticalPosition = 0;
return;
}
int location = (int)chromaSampleLocation;
horizontalPosition = ChromaLocationX[location];
verticalPosition = ChromaLocationY[location];
}
/// <summary>
/// Converts one reconstructed HEVC row using pooled planar and packed-pixel storage.
/// </summary>
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
private readonly struct YuvToRgbRowConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// The configuration used for packed-pixel conversion.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// The reconstructed HEVC picture.
/// </summary>
private readonly HevcPictureBuffer picture;
/// <summary>
/// The destination image frame.
/// </summary>
private readonly ImageFrame<TPixel> image;
/// <summary>
/// The selected H.273 color converter.
/// </summary>
private readonly HeifColorConverterBase colorConverter;
/// <summary>
/// The horizontal chroma offset in half-luma-sample units.
/// </summary>
private readonly int horizontalPosition;
/// <summary>
/// The vertical chroma offset in half-luma-sample units.
/// </summary>
private readonly int verticalPosition;
/// <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}"/> struct.
/// </summary>
/// <param name="configuration">The configuration used for pixel conversion.</param>
/// <param name="picture">The reconstructed HEVC picture.</param>
/// <param name="image">The destination image frame.</param>
/// <param name="colorConverter">The selected H.273 color converter.</param>
/// <param name="chromaSampleLocation">The progressive-frame 4:2:0 chroma sample location.</param>
public YuvToRgbRowConverter(
Configuration configuration,
HevcPictureBuffer picture,
ImageFrame<TPixel> image,
HeifColorConverterBase colorConverter,
HevcChromaSampleLocation chromaSampleLocation)
{
this.configuration = configuration;
this.picture = picture;
this.image = image;
this.colorConverter = colorConverter;
this.subX = picture.GetSubsamplingX(HevcPlane.Cb);
this.subY = picture.GetSubsamplingY(HevcPlane.Cb);
GetChromaPosition(picture, chromaSampleLocation, out this.horizontalPosition, out this.verticalPosition);
}
/// <summary>
/// Gets a value indicating whether the destination uses JPEG-compatible eight-bit RGB plane packing.
/// </summary>
public bool UsesBytePacking => this.picture.BitDepthLuma == 8 && (this.picture.ChromaFormat == 0 || this.picture.BitDepthChroma == 8);
/// <summary>
/// Gets the number of float elements required by the reusable row buffer.
/// </summary>
public int BufferLength
{
get
{
int componentRowCount = this.picture.ChromaFormat == 0 ? 3 : 5;
int packedRowCount = this.UsesBytePacking ? 1 : 2;
return this.image.Width * (componentRowCount + packedRowCount);
}
}
/// <summary>
/// Converts one reconstructed row to packed pixels.
/// </summary>
/// <param name="y">The zero-based luma row.</param>
/// <param name="scratch">The reusable pooled row buffer.</param>
/// <param name="proxy">The padded byte-packing destination when the image row has insufficient padding.</param>
public void Convert(int y, Span<float> scratch, Span<TPixel> proxy)
{
int width = this.image.Width;
Span<float> red = scratch[..width];
Span<float> green = scratch.Slice(width, width);
Span<float> blue = scratch.Slice(width * 2, width);
ConvertSamplesToFloat<ushort, HeifUShortSampleLoader>(this.picture.GetRowSpan(HevcPlane.Y, y), red);
int packedOffset = width * 3;
if (this.picture.ChromaFormat != 0)
{
int chromaHeight = this.picture.GetHeight(HevcPlane.Cb);
GetChromaCoordinates(y, this.subY, this.verticalPosition, chromaHeight - 1, out int y0, out int y1, out int y1Weight);
ReadOnlySpan<ushort> cb0 = this.picture.GetRowSpan(HevcPlane.Cb, y0);
ReadOnlySpan<ushort> cb1 = this.picture.GetRowSpan(HevcPlane.Cb, y1);
ReadOnlySpan<ushort> cr0 = this.picture.GetRowSpan(HevcPlane.Cr, y0);
ReadOnlySpan<ushort> cr1 = this.picture.GetRowSpan(HevcPlane.Cr, y1);
Span<float> chroma0 = scratch.Slice(width * 3, width);
Span<float> chroma1 = scratch.Slice(width * 4, width);
bool isCenteredX = this.subX != 0 && this.horizontalPosition == 1;
ReconstructChromaRow<ushort, HeifUShortSampleLoader>(cb0, cb1, y1Weight, this.subX, isCenteredX, green, chroma0, chroma1);
ReconstructChromaRow<ushort, HeifUShortSampleLoader>(cr0, cr1, y1Weight, this.subX, isCenteredX, blue, chroma0, chroma1);
packedOffset = width * 5;
}
this.colorConverter.ConvertToRgbInPlace(red, green, blue);
Span<TPixel> destination = this.image.PixelBuffer.DangerousGetRowSpan(y);
Span<float> packedStorage = scratch[packedOffset..];
if (this.UsesBytePacking)
{
// This is JPEG's planar packing contract. Existing pixel-specific SIMD packers therefore own the
// final RGB-to-TPixel conversion instead of a HEVC-specific per-pixel loop.
Span<byte> byteStorage = MemoryMarshal.AsBytes(packedStorage)[..(width * 3)];
Span<byte> redBytes = byteStorage[..width];
Span<byte> greenBytes = byteStorage.Slice(width, width);
Span<byte> blueBytes = byteStorage.Slice(width * 2, width);
SimdUtils.NormalizedFloatToByteSaturate(red, redBytes);
SimdUtils.NormalizedFloatToByteSaturate(green, greenBytes);
SimdUtils.NormalizedFloatToByteSaturate(blue, blueBytes);
if (this.image.PixelBuffer.DangerousTryGetPaddedRowSpan(y, 3, out Span<TPixel> paddedDestination))
{
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(redBytes, greenBytes, blueBytes, paddedDestination);
}
else
{
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(redBytes, greenBytes, blueBytes, proxy);
proxy[..width].CopyTo(destination);
}
return;
}
Span<Rgba64> packed = MemoryMarshal.Cast<float, Rgba64>(packedStorage)[..width];
PackRgba64(red, green, blue, packed);
PixelOperations<TPixel>.Instance.FromRgba64(this.configuration, packed, destination);
}
}
/// <summary>
/// Converts packed image rows to HEVC component planes using pooled planar storage.
/// </summary>
/// <typeparam name="TPixel">The source pixel type.</typeparam>
private readonly struct RgbToYuvRowConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// The configuration used for packed-pixel conversion.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// The destination HEVC picture.
/// </summary>
private readonly HevcPictureBuffer picture;
/// <summary>
/// The source image frame.
/// </summary>
private readonly ImageFrame<TPixel> image;
/// <summary>
/// The selected H.273 color converter.
/// </summary>
private readonly HeifColorConverterBase colorConverter;
/// <summary>
/// The largest encoded luma sample value.
/// </summary>
private readonly float lumaMaximum;
/// <summary>
/// The largest encoded chroma sample value.
/// </summary>
private readonly float chromaMaximum;
/// <summary>
/// The horizontal chroma offset in half-luma-sample units.
/// </summary>
private readonly int horizontalPosition;
/// <summary>
/// The vertical chroma offset in half-luma-sample units.
/// </summary>
private readonly int verticalPosition;
/// <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="RgbToYuvRowConverter{TPixel}"/> struct.
/// </summary>
/// <param name="configuration">The configuration used for pixel conversion.</param>
/// <param name="picture">The destination HEVC picture.</param>
/// <param name="image">The source image frame.</param>
/// <param name="colorConverter">The selected H.273 color converter.</param>
/// <param name="chromaSampleLocation">The progressive-frame 4:2:0 chroma sample location.</param>
/// <param name="parameters">The resolved H.273 component ranges.</param>
public RgbToYuvRowConverter(
Configuration configuration,
HevcPictureBuffer picture,
ImageFrame<TPixel> image,
HeifColorConverterBase colorConverter,
HevcChromaSampleLocation chromaSampleLocation,
in HeifColorConversionParameters parameters)
{
this.configuration = configuration;
this.picture = picture;
this.image = image;
this.colorConverter = colorConverter;
this.lumaMaximum = parameters.LumaSampleMaximum;
this.chromaMaximum = parameters.ChromaSampleMaximum;
this.subX = picture.GetSubsamplingX(HevcPlane.Cb);
this.subY = picture.GetSubsamplingY(HevcPlane.Cb);
GetChromaPosition(picture, chromaSampleLocation, out this.horizontalPosition, out this.verticalPosition);
}
/// <summary>
/// Gets a value indicating whether source pixels use JPEG-compatible eight-bit RGB plane unpacking.
/// </summary>
public bool UsesByteInput => this.picture.BitDepthLuma == 8 && (this.picture.ChromaFormat == 0 || this.picture.BitDepthChroma == 8);
/// <summary>
/// Gets the number of float elements required by the reusable component buffer.
/// </summary>
public int ComponentBufferLength => this.image.Width * (this.subY == 0 ? 3 : 6);
/// <summary>
/// Converts every packed source row to the destination component planes.
/// </summary>
/// <param name="packed">The reusable high-bit-depth RGB staging row.</param>
/// <param name="components">The reusable planar component buffer.</param>
public void Convert(Span<Rgb48> packed, Span<float> components)
{
int width = this.image.Width;
for (int y = 0; y < this.image.Height; y++)
{
int slot = this.subY == 0 ? 0 : y & 1;
int offset = slot * width * 3;
Span<float> luma = components.Slice(offset, width);
Span<float> chromaBlue = components.Slice(offset + width, width);
Span<float> chromaRed = components.Slice(offset + (width * 2), width);
this.ConvertSourceRow(y, packed, luma, chromaBlue, chromaRed);
WriteSamples<ushort, HeifUShortSampleStorer>(
luma,
this.picture.GetRowSpan(HevcPlane.Y, y),
this.colorConverter.LumaScale,
this.colorConverter.LumaBias,
this.lumaMaximum);
if (this.picture.ChromaFormat == 0)
{
continue;
}
if (this.subY == 0)
{
this.WriteChromaRows(y, chromaBlue, chromaRed, Span<float>.Empty, Span<float>.Empty, 0F);
continue;
}
bool isLastRow = y == this.image.Height - 1;
if (this.verticalPosition == 0 && (y & 1) == 0)
{
this.WriteChromaRows(y >> 1, chromaBlue, chromaRed, Span<float>.Empty, Span<float>.Empty, 0F);
}
else if (this.verticalPosition == 1 && ((y & 1) != 0 || isLastRow))
{
if ((y & 1) != 0)
{
Span<float> previousBlue = components.Slice(width, width);
Span<float> previousRed = components.Slice(width * 2, width);
this.WriteChromaRows(y >> 1, previousBlue, previousRed, chromaBlue, chromaRed, 0.5F);
}
else
{
this.WriteChromaRows(y >> 1, chromaBlue, chromaRed, Span<float>.Empty, Span<float>.Empty, 0F);
}
}
else if (this.verticalPosition == 2 && ((y & 1) != 0 || isLastRow))
{
this.WriteChromaRows(y >> 1, chromaBlue, chromaRed, Span<float>.Empty, Span<float>.Empty, 0F);
}
}
}
/// <summary>
/// Converts one packed source row into normalized planar 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 or first component values.</param>
/// <param name="chromaBlue">The destination blue-difference or second component values.</param>
/// <param name="chromaRed">The destination red-difference or third component 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 (this.UsesByteInput)
{
// JPEG's planar unpack contract reaches the existing pixel-specific SIMD implementation before
// the closed H.273 operator transforms the rows in place.
PixelOperations<TPixel>.Instance.UnpackIntoRgbPlanes(luma, chromaBlue, chromaRed, source);
this.colorConverter.ConvertFromRgbInPlace(luma, chromaBlue, chromaRed, ByteMaximum);
return;
}
PixelOperations<TPixel>.Instance.ToRgb48(this.configuration, source, packed);
DeinterleaveRgb48(packed, luma, chromaBlue, chromaRed);
this.colorConverter.ConvertFromRgbInPlace(luma, chromaBlue, chromaRed, UShortMaximum);
}
/// <summary>
/// Filters and writes one pair of chroma component rows.
/// </summary>
/// <param name="destinationY">The destination chroma row.</param>
/// <param name="blue0">The first blue-difference source row.</param>
/// <param name="red0">The first red-difference source row.</param>
/// <param name="blue1">The optional second blue-difference source row.</param>
/// <param name="red1">The optional second red-difference source row.</param>
/// <param name="row1Weight">The second-row contribution.</param>
private void WriteChromaRows(
int destinationY,
ReadOnlySpan<float> blue0,
ReadOnlySpan<float> red0,
ReadOnlySpan<float> blue1,
ReadOnlySpan<float> red1,
float row1Weight)
{
Span<ushort> blueDestination = this.picture.GetRowSpan(HevcPlane.Cb, destinationY);
Span<ushort> redDestination = this.picture.GetRowSpan(HevcPlane.Cr, destinationY);
if (this.subX == 0)
{
WriteSamples<ushort, HeifUShortSampleStorer>(
blue0,
blueDestination,
this.colorConverter.ChromaScale,
this.colorConverter.ChromaBias,
this.chromaMaximum);
WriteSamples<ushort, HeifUShortSampleStorer>(
red0,
redDestination,
this.colorConverter.ChromaScale,
this.colorConverter.ChromaBias,
this.chromaMaximum);
return;
}
bool isCenteredX = this.horizontalPosition == 1;
WriteSubsampledSamples<ushort, HeifUShortSampleStorer>(
blue0,
blue1,
blueDestination,
isCenteredX,
row1Weight,
this.colorConverter.ChromaScale,
this.colorConverter.ChromaBias,
this.chromaMaximum);
WriteSubsampledSamples<ushort, HeifUShortSampleStorer>(
red0,
red1,
redDestination,
isCenteredX,
row1Weight,
this.colorConverter.ChromaScale,
this.colorConverter.ChromaBias,
this.chromaMaximum);
}
}
}

185
tests/ImageSharp.Benchmarks/Codecs/Heif/HevcColorConversionBenchmarks.cs

@ -0,0 +1,185 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Heif.Hevc;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif;
/// <summary>
/// Measures frame-wide HEVC YUV 4:2:0 color conversion in both directions.
/// </summary>
[MemoryDiagnoser(displayGenColumns: false)]
public class HevcColorConversionBenchmarks
{
/// <summary>
/// The benchmark frame width.
/// </summary>
private const int Width = 1920;
/// <summary>
/// The benchmark frame height.
/// </summary>
private const int Height = 1080;
/// <summary>
/// The eight-bit source RGB image.
/// </summary>
private Image<Rgba32> byteSource = null!;
/// <summary>
/// The eight-bit destination RGB image.
/// </summary>
private Image<Rgba32> byteDestination = null!;
/// <summary>
/// The high-bit-depth source RGB image.
/// </summary>
private Image<Rgb48> highBitDepthSource = null!;
/// <summary>
/// The high-bit-depth destination RGB image.
/// </summary>
private Image<Rgb48> highBitDepthDestination = null!;
/// <summary>
/// The reusable HEVC component planes.
/// </summary>
private HevcPictureBuffer picture = null!;
/// <summary>
/// The H.273 profile used by both conversion directions.
/// </summary>
private CicpProfile colorProfile = null!;
/// <summary>
/// Gets or sets the encoded HEVC 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.byteSource = new Image<Rgba32>(Width, Height);
this.byteDestination = new Image<Rgba32>(Width, Height);
this.highBitDepthSource = new Image<Rgb48>(Width, Height);
this.highBitDepthDestination = new Image<Rgb48>(Width, Height);
for (int y = 0; y < Height; y++)
{
Span<Rgba32> byteRow = this.byteSource.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y);
Span<Rgb48> highBitDepthRow = this.highBitDepthSource.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y);
for (int x = 0; x < Width; x++)
{
// Relatively prime channel steps exercise the row-wide converter without introducing setup randomness.
byteRow[x] = new Rgba32((byte)((x * 29) + (y * 11)), (byte)((x * 17) + (y * 31)), (byte)((x * 7) + (y * 43)));
highBitDepthRow[x] = new Rgb48(
(ushort)((x * 1879) + (y * 791)),
(ushort)((x * 977) + (y * 3251)),
(ushort)((x * 613) + (y * 4987)));
}
}
this.colorProfile = new CicpProfile(
(byte)CicpColorPrimaries.ItuRBt709_6,
(byte)CicpTransferCharacteristics.ItuRBt709_6,
(byte)CicpMatrixCoefficients.ItuRBt709_6,
false);
this.picture = new HevcPictureBuffer(Configuration.Default, Width, Height, this.BitDepth, this.BitDepth, 1, false);
if (this.BitDepth == 8)
{
HevcYuvConverter.ConvertFromRgb(
Configuration.Default,
this.byteSource.Frames.RootFrame,
this.picture,
this.colorProfile,
HevcChromaSampleLocation.Left);
}
else
{
HevcYuvConverter.ConvertFromRgb(
Configuration.Default,
this.highBitDepthSource.Frames.RootFrame,
this.picture,
this.colorProfile,
HevcChromaSampleLocation.Left);
}
}
/// <summary>
/// Releases the benchmark images and component planes.
/// </summary>
[GlobalCleanup]
public void Cleanup()
{
this.picture.Dispose();
this.highBitDepthDestination.Dispose();
this.highBitDepthSource.Dispose();
this.byteDestination.Dispose();
this.byteSource.Dispose();
}
/// <summary>
/// Measures full-frame YUV-to-RGB conversion, including chroma reconstruction and packed-pixel conversion.
/// </summary>
/// <returns>A converted component that keeps the frame result observable.</returns>
[Benchmark]
public ushort ConvertToRgb()
{
if (this.BitDepth == 8)
{
HevcYuvConverter.ConvertToRgb(
Configuration.Default,
this.picture,
this.byteDestination.Frames.RootFrame,
this.colorProfile,
HevcChromaSampleLocation.Left);
return this.byteDestination[Width - 1, Height - 1].R;
}
HevcYuvConverter.ConvertToRgb(
Configuration.Default,
this.picture,
this.highBitDepthDestination.Frames.RootFrame,
this.colorProfile,
HevcChromaSampleLocation.Left);
return this.highBitDepthDestination[Width - 1, Height - 1].R;
}
/// <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 ushort ConvertFromRgb()
{
if (this.BitDepth == 8)
{
HevcYuvConverter.ConvertFromRgb(
Configuration.Default,
this.byteSource.Frames.RootFrame,
this.picture,
this.colorProfile,
HevcChromaSampleLocation.Left);
}
else
{
HevcYuvConverter.ConvertFromRgb(
Configuration.Default,
this.highBitDepthSource.Frames.RootFrame,
this.picture,
this.colorProfile,
HevcChromaSampleLocation.Left);
}
return this.picture.GetRowSpan(HevcPlane.Y, Height - 1)[Width - 1];
}
}

74
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransferFunctionsTests.cs → tests/ImageSharp.Tests/Formats/Heif/Color/HeifTransferFunctionsTests.cs

@ -2,16 +2,16 @@
// Licensed under the Six Labors Split License.
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Color;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Color;
/// <summary>
/// Verifies scalar and SIMD parity for every H.273 transfer characteristic consumed by AV1 color conversion.
/// Verifies scalar and SIMD parity for every H.273 transfer characteristic consumed by HEIF color conversion.
/// </summary>
[Trait("Format", "Avif")]
public class Av1TransferFunctionsTests
[Trait("Format", "Heif")]
public class HeifTransferFunctionsTests
{
private static readonly float[] SignalValues =
[
@ -38,23 +38,23 @@ public class Av1TransferFunctionsTests
/// </summary>
public static TheoryData<int> TransferCharacteristics { get; } = new()
{
(int)ObuTransferCharacteristics.Bt709,
(int)ObuTransferCharacteristics.Unspecified,
(int)ObuTransferCharacteristics.Bt470M,
(int)ObuTransferCharacteristics.Bt470BG,
(int)ObuTransferCharacteristics.Bt601,
(int)ObuTransferCharacteristics.Smpte240,
(int)ObuTransferCharacteristics.Linear,
(int)ObuTransferCharacteristics.Log100,
(int)ObuTransferCharacteristics.Log100Sqrt10,
(int)ObuTransferCharacteristics.Iec61966,
(int)ObuTransferCharacteristics.Bt1361,
(int)ObuTransferCharacteristics.Srgb,
(int)ObuTransferCharacteristics.Bt202010Bit,
(int)ObuTransferCharacteristics.Bt202012Bit,
(int)ObuTransferCharacteristics.Smpte2084,
(int)ObuTransferCharacteristics.Smpte428,
(int)ObuTransferCharacteristics.Hlg,
(int)CicpTransferCharacteristics.ItuRBt709_6,
(int)CicpTransferCharacteristics.Unspecified,
(int)CicpTransferCharacteristics.Gamma2_2,
(int)CicpTransferCharacteristics.Gamma2_8,
(int)CicpTransferCharacteristics.ItuRBt601_7,
(int)CicpTransferCharacteristics.SmpteSt240,
(int)CicpTransferCharacteristics.Linear,
(int)CicpTransferCharacteristics.Log100,
(int)CicpTransferCharacteristics.Log100Sqrt,
(int)CicpTransferCharacteristics.Iec61966_2_4,
(int)CicpTransferCharacteristics.ItuRBt1361_0,
(int)CicpTransferCharacteristics.Iec61966_2_1,
(int)CicpTransferCharacteristics.ItuRBt2020_2_10bit,
(int)CicpTransferCharacteristics.ItuRBt2020_2_12bit,
(int)CicpTransferCharacteristics.SmpteSt2084,
(int)CicpTransferCharacteristics.SmpteSt428_1,
(int)CicpTransferCharacteristics.AribStdB67,
};
/// <summary>
@ -65,12 +65,12 @@ public class Av1TransferFunctionsTests
[MemberData(nameof(TransferCharacteristics))]
public void ToLinearSimdMatchesScalar(int transferCharacteristicsValue)
{
ObuTransferCharacteristics transferCharacteristics = (ObuTransferCharacteristics)transferCharacteristicsValue;
float[] expected = SignalValues.Select(value => Av1TransferFunctions.ToLinear(transferCharacteristics, value)).ToArray();
CicpTransferCharacteristics transferCharacteristics = (CicpTransferCharacteristics)transferCharacteristicsValue;
float[] expected = SignalValues.Select(value => HeifTransferFunctions.ToLinear(transferCharacteristics, value)).ToArray();
Vector128<float> vector128 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128<float>.Count)));
Vector256<float> vector256 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256<float>.Count)));
Vector512<float> vector512 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector512.Create(SignalValues));
Vector128<float> vector128 = HeifTransferFunctions.ToLinear(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128<float>.Count)));
Vector256<float> vector256 = HeifTransferFunctions.ToLinear(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256<float>.Count)));
Vector512<float> vector512 = HeifTransferFunctions.ToLinear(transferCharacteristics, Vector512.Create(SignalValues));
AssertVectorMatchesScalar(expected, vector128, transferCharacteristics);
AssertVectorMatchesScalar(expected, vector256, transferCharacteristics);
@ -85,12 +85,12 @@ public class Av1TransferFunctionsTests
[MemberData(nameof(TransferCharacteristics))]
public void ToGammaSimdMatchesScalar(int transferCharacteristicsValue)
{
ObuTransferCharacteristics transferCharacteristics = (ObuTransferCharacteristics)transferCharacteristicsValue;
float[] expected = SignalValues.Select(value => Av1TransferFunctions.ToGamma(transferCharacteristics, value)).ToArray();
CicpTransferCharacteristics transferCharacteristics = (CicpTransferCharacteristics)transferCharacteristicsValue;
float[] expected = SignalValues.Select(value => HeifTransferFunctions.ToGamma(transferCharacteristics, value)).ToArray();
Vector128<float> vector128 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128<float>.Count)));
Vector256<float> vector256 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256<float>.Count)));
Vector512<float> vector512 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector512.Create(SignalValues));
Vector128<float> vector128 = HeifTransferFunctions.ToGamma(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128<float>.Count)));
Vector256<float> vector256 = HeifTransferFunctions.ToGamma(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256<float>.Count)));
Vector512<float> vector512 = HeifTransferFunctions.ToGamma(transferCharacteristics, Vector512.Create(SignalValues));
AssertVectorMatchesScalar(expected, vector128, transferCharacteristics);
AssertVectorMatchesScalar(expected, vector256, transferCharacteristics);
@ -103,7 +103,7 @@ public class Av1TransferFunctionsTests
/// <param name="expected">The scalar results.</param>
/// <param name="actual">The SIMD results.</param>
/// <param name="transferCharacteristics">The transfer characteristic under test.</param>
private static void AssertVectorMatchesScalar(ReadOnlySpan<float> expected, Vector128<float> actual, ObuTransferCharacteristics transferCharacteristics)
private static void AssertVectorMatchesScalar(ReadOnlySpan<float> expected, Vector128<float> actual, CicpTransferCharacteristics transferCharacteristics)
{
for (int i = 0; i < Vector128<float>.Count; i++)
{
@ -117,7 +117,7 @@ public class Av1TransferFunctionsTests
/// <param name="expected">The scalar results.</param>
/// <param name="actual">The SIMD results.</param>
/// <param name="transferCharacteristics">The transfer characteristic under test.</param>
private static void AssertVectorMatchesScalar(ReadOnlySpan<float> expected, Vector256<float> actual, ObuTransferCharacteristics transferCharacteristics)
private static void AssertVectorMatchesScalar(ReadOnlySpan<float> expected, Vector256<float> actual, CicpTransferCharacteristics transferCharacteristics)
{
for (int i = 0; i < Vector256<float>.Count; i++)
{
@ -131,7 +131,7 @@ public class Av1TransferFunctionsTests
/// <param name="expected">The scalar results.</param>
/// <param name="actual">The SIMD results.</param>
/// <param name="transferCharacteristics">The transfer characteristic under test.</param>
private static void AssertVectorMatchesScalar(ReadOnlySpan<float> expected, Vector512<float> actual, ObuTransferCharacteristics transferCharacteristics)
private static void AssertVectorMatchesScalar(ReadOnlySpan<float> expected, Vector512<float> actual, CicpTransferCharacteristics transferCharacteristics)
{
for (int i = 0; i < Vector512<float>.Count; i++)
{
@ -147,7 +147,7 @@ public class Av1TransferFunctionsTests
/// <param name="transferCharacteristics">The transfer characteristic under test.</param>
/// <param name="lane">The SIMD lane index.</param>
/// <param name="width">The SIMD register width.</param>
private static void AssertClose(float expected, float actual, ObuTransferCharacteristics transferCharacteristics, int lane, int width)
private static void AssertClose(float expected, float actual, CicpTransferCharacteristics transferCharacteristics, int lane, int width)
{
float tolerance = MathF.Max(2E-5F, MathF.Abs(expected) * 2E-5F);
Assert.True(

303
tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcYuvConverterTests.cs

@ -0,0 +1,303 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Hevc;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc;
/// <summary>
/// Verifies the shared SIMD-first HEVC component conversion pipeline in both directions.
/// </summary>
[Trait("Format", "Heic")]
public class HevcYuvConverterTests
{
/// <summary>
/// Verifies that the identity matrix preserves every eight-bit RGB component through the HEVC GBR plane order.
/// </summary>
[Fact]
public void IdentityRoundTripIsExact()
{
const int width = 19;
const int height = 7;
CicpProfile profile = CreateProfile(CicpMatrixCoefficients.Identity, true);
using Image<Rgba32> source = new(width, height);
for (int y = 0; y < height; y++)
{
Span<Rgba32> row = source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y);
for (int x = 0; x < width; x++)
{
row[x] = new Rgba32((byte)((x * 17) + y), (byte)((y * 31) + x), (byte)((x * 7) + (y * 13)));
}
}
using HevcPictureBuffer picture = new(Configuration.Default, width, height, 8, 8, 3, false);
HevcYuvConverter.ConvertFromRgb(
Configuration.Default,
source.Frames.RootFrame,
picture,
profile,
HevcChromaSampleLocation.Left);
using Image<Rgba32> destination = new(width, height);
HevcYuvConverter.ConvertToRgb(
Configuration.Default,
picture,
destination.Frames.RootFrame,
profile,
HevcChromaSampleLocation.Left);
for (int y = 0; y < height; y++)
{
ReadOnlySpan<Rgba32> expected = source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y);
ReadOnlySpan<Rgba32> actual = destination.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y);
Assert.True(expected.SequenceEqual(actual));
}
}
/// <summary>
/// Verifies that every exposed precision and plane layout executes both conversion directions on an odd-sized image.
/// </summary>
/// <param name="bitDepth">The encoded component precision.</param>
/// <param name="chromaFormat">The HEVC chroma-format identifier.</param>
/// <param name="fullRange">Whether components use their complete numeric range.</param>
[Theory]
[InlineData(8, 0, true)]
[InlineData(8, 1, false)]
[InlineData(10, 2, true)]
[InlineData(12, 3, false)]
public void ConstantColorRoundTripsEveryPrecisionAndPlaneLayout(int bitDepth, byte chromaFormat, bool fullRange)
{
const int width = 17;
const int height = 9;
Rgba64 expected = new(38550, 25700, 51400, ushort.MaxValue);
CicpProfile profile = CreateProfile(CicpMatrixCoefficients.ItuRBt709_6, fullRange);
using Image<Rgba64> source = new(width, height, expected);
using HevcPictureBuffer picture = new(Configuration.Default, width, height, bitDepth, bitDepth, chromaFormat, false);
HevcYuvConverter.ConvertFromRgb(
Configuration.Default,
source.Frames.RootFrame,
picture,
profile,
HevcChromaSampleLocation.Left);
using Image<Rgba64> destination = new(width, height);
HevcYuvConverter.ConvertToRgb(
Configuration.Default,
picture,
destination.Frames.RootFrame,
profile,
HevcChromaSampleLocation.Left);
int tolerance = bitDepth switch
{
8 => 800,
10 => 240,
_ => 80,
};
foreach (Rgba64 actual in destination.Frames.RootFrame.PixelBuffer.DangerousGetSingleSpan())
{
if (chromaFormat == 0)
{
// A monochrome picture retains only luma. The decoded channels must therefore agree, while
// comparing them with the chromatic source would incorrectly require discarded color to survive.
Assert.InRange(Math.Abs(actual.R - actual.G), 0, tolerance);
Assert.InRange(Math.Abs(actual.R - actual.B), 0, tolerance);
Assert.Equal(ushort.MaxValue, actual.A);
continue;
}
Assert.InRange(Math.Abs(actual.R - expected.R), 0, tolerance);
Assert.InRange(Math.Abs(actual.G - expected.G), 0, tolerance);
Assert.InRange(Math.Abs(actual.B - expected.B), 0, tolerance);
Assert.Equal(ushort.MaxValue, actual.A);
}
}
/// <summary>
/// Verifies the six HEVC 4:2:0 sample locations in both conversion directions.
/// </summary>
/// <param name="chromaSampleLocationValue">The signaled sample-location code point.</param>
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(5)]
public void ConvertsEveryChromaSampleLocation(int chromaSampleLocationValue)
{
const int width = 17;
const int height = 9;
Rgba64 expected = new(46260, 20560, 33410, ushort.MaxValue);
CicpProfile profile = CreateProfile(CicpMatrixCoefficients.ItuRBt709_6, true);
HevcChromaSampleLocation chromaSampleLocation = (HevcChromaSampleLocation)chromaSampleLocationValue;
using Image<Rgba64> source = new(width, height, expected);
using HevcPictureBuffer picture = new(Configuration.Default, width, height, 10, 10, 1, false);
HevcYuvConverter.ConvertFromRgb(Configuration.Default, source.Frames.RootFrame, picture, profile, chromaSampleLocation);
using Image<Rgba64> destination = new(width, height);
HevcYuvConverter.ConvertToRgb(Configuration.Default, picture, destination.Frames.RootFrame, profile, chromaSampleLocation);
Rgba64 actual = destination[width - 1, height - 1];
Assert.InRange(Math.Abs(actual.R - expected.R), 0, 240);
Assert.InRange(Math.Abs(actual.G - expected.G), 0, 240);
Assert.InRange(Math.Abs(actual.B - expected.B), 0, 240);
}
/// <summary>
/// Verifies that encoding selects the horizontal and vertical sample coordinates defined by all six location code points.
/// </summary>
[Fact]
public void ChromaSampleLocationsSelectExpectedSourceCoordinatesWhenEncoding()
{
CicpProfile profile = CreateProfile(CicpMatrixCoefficients.YCgCo, true);
using Image<Rgba32> source = new(2, 2);
source[0, 0] = new Rgba32(0, 0, 0);
source[1, 0] = new Rgba32(0, byte.MaxValue, 0);
source[0, 1] = new Rgba32(byte.MaxValue, 0, 0);
source[1, 1] = new Rgba32(0, 0, byte.MaxValue);
// YCgCo maps the four source pixels to neutral, positive Cg, positive Co, and negative Co. Applying the
// H.273 full-range scale after the location-specific point or average yields these exact encoded samples.
ReadOnlySpan<ushort> expectedChromaBlue = [96, 128, 128, 192, 64, 64];
ReadOnlySpan<ushort> expectedChromaRed = [192, 128, 128, 128, 255, 128];
for (int location = 0; location < expectedChromaBlue.Length; location++)
{
using HevcPictureBuffer picture = new(Configuration.Default, 2, 2, 8, 8, 1, false);
HevcYuvConverter.ConvertFromRgb(
Configuration.Default,
source.Frames.RootFrame,
picture,
profile,
(HevcChromaSampleLocation)location);
Assert.Equal(expectedChromaBlue[location], picture.GetRowSpan(HevcPlane.Cb, 0)[0]);
Assert.Equal(expectedChromaRed[location], picture.GetRowSpan(HevcPlane.Cr, 0)[0]);
}
}
/// <summary>
/// Verifies that decoding reconstructs chroma at the coordinates defined by all six location code points.
/// </summary>
[Fact]
public void ChromaSampleLocationsInterpolateExpectedCoordinatesWhenDecoding()
{
CicpProfile profile = CreateProfile(CicpMatrixCoefficients.YCgCo, true);
// At luma coordinate (1, 1), the six locations exercise centered and co-sited horizontal reconstruction
// together with the quarter-sample weights for centered, top, and bottom vertical positions.
ReadOnlySpan<byte> expectedRed = [96, 128, 128, 160, 64, 96];
ReadOnlySpan<byte> expectedGreen = [160, 128, 128, 96, 192, 160];
for (int location = 0; location < expectedRed.Length; location++)
{
using HevcPictureBuffer picture = new(Configuration.Default, 4, 4, 8, 8, 1, false);
picture.Luma.DangerousGetSingleSpan().Fill(128);
picture.ChromaRed!.DangerousGetSingleSpan().Fill(128);
Span<ushort> topChromaBlue = picture.GetRowSpan(HevcPlane.Cb, 0);
Span<ushort> bottomChromaBlue = picture.GetRowSpan(HevcPlane.Cb, 1);
topChromaBlue[0] = 128;
topChromaBlue[1] = 255;
bottomChromaBlue[0] = 0;
bottomChromaBlue[1] = 128;
using Image<Rgba32> destination = new(4, 4);
HevcYuvConverter.ConvertToRgb(
Configuration.Default,
picture,
destination.Frames.RootFrame,
profile,
(HevcChromaSampleLocation)location);
Rgba32 actual = destination[1, 1];
Assert.InRange(Math.Abs(actual.R - expectedRed[location]), 0, 1);
Assert.InRange(Math.Abs(actual.G - expectedGreen[location]), 0, 1);
Assert.InRange(Math.Abs(actual.B - expectedRed[location]), 0, 1);
}
}
/// <summary>
/// Verifies that independent luma and chroma precisions use their own H.273 code ranges.
/// </summary>
[Fact]
public void SupportsIndependentLumaAndChromaBitDepths()
{
CicpProfile profile = CreateProfile(CicpMatrixCoefficients.ItuRBt709_6, false);
using Image<Rgba64> source = new(17, 5, new Rgba64(40000, 24000, 48000, ushort.MaxValue));
using HevcPictureBuffer picture = new(Configuration.Default, 17, 5, 10, 12, 3, false);
HevcYuvConverter.ConvertFromRgb(
Configuration.Default,
source.Frames.RootFrame,
picture,
profile,
HevcChromaSampleLocation.Left);
foreach (ushort sample in picture.Luma.DangerousGetSingleSpan())
{
Assert.InRange(sample, (ushort)0, (ushort)1023);
}
foreach (ushort sample in picture.ChromaBlue!.DangerousGetSingleSpan())
{
Assert.InRange(sample, (ushort)0, (ushort)4095);
}
foreach (ushort sample in picture.ChromaRed!.DangerousGetSingleSpan())
{
Assert.InRange(sample, (ushort)0, (ushort)4095);
}
using Image<Rgba64> destination = new(17, 5);
HevcYuvConverter.ConvertToRgb(
Configuration.Default,
picture,
destination.Frames.RootFrame,
profile,
HevcChromaSampleLocation.Left);
}
/// <summary>
/// Verifies the exact limited-range endpoints written for ten-bit luma and chroma samples.
/// </summary>
[Fact]
public void LimitedRangeUsesExactTenBitEndpoints()
{
CicpProfile profile = CreateProfile(CicpMatrixCoefficients.ItuRBt709_6, false);
using Image<Rgba64> source = new(2, 1);
source[0, 0] = new Rgba64(0, 0, 0, ushort.MaxValue);
source[1, 0] = new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue);
using HevcPictureBuffer picture = new(Configuration.Default, 2, 1, 10, 10, 3, false);
HevcYuvConverter.ConvertFromRgb(
Configuration.Default,
source.Frames.RootFrame,
picture,
profile,
HevcChromaSampleLocation.Left);
Assert.Equal((ushort)64, picture.GetRowSpan(HevcPlane.Y, 0)[0]);
Assert.Equal((ushort)940, picture.GetRowSpan(HevcPlane.Y, 0)[1]);
Assert.Equal((ushort)512, picture.GetRowSpan(HevcPlane.Cb, 0)[0]);
Assert.Equal((ushort)512, picture.GetRowSpan(HevcPlane.Cb, 0)[1]);
Assert.Equal((ushort)512, picture.GetRowSpan(HevcPlane.Cr, 0)[0]);
Assert.Equal((ushort)512, picture.GetRowSpan(HevcPlane.Cr, 0)[1]);
}
/// <summary>
/// Creates a complete H.273 profile for a conversion test.
/// </summary>
/// <param name="matrixCoefficients">The matrix-coefficient code point.</param>
/// <param name="fullRange">Whether components use their complete numeric range.</param>
/// <returns>The configured profile.</returns>
private static CicpProfile CreateProfile(CicpMatrixCoefficients matrixCoefficients, bool fullRange)
=> new(
(byte)CicpColorPrimaries.ItuRBt709_6,
(byte)CicpTransferCharacteristics.ItuRBt709_6,
(byte)matrixCoefficients,
fullRange);
}
Loading…
Cancel
Save