From 7b1bb3e8ed576a1659b14a7279dfc544655b0ce9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 26 Aug 2026 00:15:01 +1000 Subject: [PATCH] Implement shared HEIF color conversion --- HEIF_IMPLEMENTATION_PLAN.md | 20 +- .../Heif/Av1/Av1ColorConversionParameters.cs | 187 ------ .../Heif/Av1/Av1YuvConverter.RowConverters.cs | 36 +- .../Formats/Heif/Av1/Av1YuvConverter.cs | 349 +---------- .../Color/HeifColorConversionParameters.cs | 498 +++++++++++++++ ...HeifColorConverter.CoefficientOperator.cs} | 22 +- ...lorConverter.ConstantLuminanceOperator.cs} | 102 +-- .../HeifColorConverter.ICtCpOperator.cs} | 136 ++-- .../HeifColorConverter.IdentityOperator.cs} | 22 +- .../HeifColorConverter.IptC2Operator.cs} | 118 ++-- .../HeifColorConverter.Operator.cs} | 44 +- .../HeifColorConverter.Samples.cs} | 192 ++++-- .../HeifColorConverter.Smpte2085Operator.cs} | 22 +- .../HeifColorConverter.YCgCoOperator.cs} | 22 +- ...ColorConverter.YCgCoReversibleOperator.cs} | 42 +- .../HeifColorConverter.cs} | 36 +- .../HeifTransferFunctions.Simd.cs} | 92 +-- .../HeifTransferFunctions.cs} | 76 +-- .../Heif/Hevc/HevcChromaSampleLocation.cs | 40 ++ .../Formats/Heif/Hevc/HevcPictureBuffer.cs | 41 +- .../Hevc/HevcVideoUsabilityInformation.cs | 8 +- .../Formats/Heif/Hevc/HevcYuvConverter.cs | 592 ++++++++++++++++++ .../Heif/HevcColorConversionBenchmarks.cs | 185 ++++++ .../HeifTransferFunctionsTests.cs} | 74 +-- .../Heif/Hevc/HevcYuvConverterTests.cs | 303 +++++++++ 25 files changed, 2263 insertions(+), 996 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1ColorConversionParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Color/HeifColorConversionParameters.cs rename src/ImageSharp/Formats/Heif/{Av1/Av1ColorConverter.CoefficientOperator.cs => Color/HeifColorConverter.CoefficientOperator.cs} (88%) rename src/ImageSharp/Formats/Heif/{Av1/Av1ColorConverter.ConstantLuminanceOperator.cs => Color/HeifColorConverter.ConstantLuminanceOperator.cs} (66%) rename src/ImageSharp/Formats/Heif/{Av1/Av1ColorConverter.ICtCpOperator.cs => Color/HeifColorConverter.ICtCpOperator.cs} (74%) rename src/ImageSharp/Formats/Heif/{Av1/Av1ColorConverter.IdentityOperator.cs => Color/HeifColorConverter.IdentityOperator.cs} (85%) rename src/ImageSharp/Formats/Heif/{Av1/Av1ColorConverter.IptC2Operator.cs => Color/HeifColorConverter.IptC2Operator.cs} (75%) rename src/ImageSharp/Formats/Heif/{Av1/Av1ColorConverter.Operator.cs => Color/HeifColorConverter.Operator.cs} (93%) rename src/ImageSharp/Formats/Heif/{Av1/Av1YuvConverter.Simd.cs => Color/HeifColorConverter.Samples.cs} (83%) rename src/ImageSharp/Formats/Heif/{Av1/Av1ColorConverter.Smpte2085Operator.cs => Color/HeifColorConverter.Smpte2085Operator.cs} (87%) rename src/ImageSharp/Formats/Heif/{Av1/Av1ColorConverter.YCgCoOperator.cs => Color/HeifColorConverter.YCgCoOperator.cs} (85%) rename src/ImageSharp/Formats/Heif/{Av1/Av1ColorConverter.YCgCoReversibleOperator.cs => Color/HeifColorConverter.YCgCoReversibleOperator.cs} (90%) rename src/ImageSharp/Formats/Heif/{Av1/Av1ColorConverter.cs => Color/HeifColorConverter.cs} (68%) rename src/ImageSharp/Formats/Heif/{Av1/Av1TransferFunctions.Simd.cs => Color/HeifTransferFunctions.Simd.cs} (93%) rename src/ImageSharp/Formats/Heif/{Av1/Av1TransferFunctions.cs => Color/HeifTransferFunctions.cs} (85%) create mode 100644 src/ImageSharp/Formats/Heif/Hevc/HevcChromaSampleLocation.cs create mode 100644 src/ImageSharp/Formats/Heif/Hevc/HevcYuvConverter.cs create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Heif/HevcColorConversionBenchmarks.cs rename tests/ImageSharp.Tests/Formats/Heif/{Av1/Av1TransferFunctionsTests.cs => Color/HeifTransferFunctionsTests.cs} (62%) create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcYuvConverterTests.cs diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index a27d400240..ffe49bc14e 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/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. diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConversionParameters.cs b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConversionParameters.cs deleted file mode 100644 index 79474a252c..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConversionParameters.cs +++ /dev/null @@ -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; - -/// -/// Stores the resolved H.273 values shared by every scalar and SIMD lane. -/// -internal readonly struct Av1ColorConversionParameters -{ - /// - /// Initializes a new instance of the struct. - /// - /// The red luma coefficient. - /// The green luma coefficient. - /// The blue luma coefficient. - /// The signaled transfer characteristics. - /// The constant-luminance chroma scales. - /// The encoded luma bias. - /// The encoded luma range. - /// The encoded chroma midpoint. - /// The encoded chroma range. - /// The largest encoded sample value. - /// The reversible transform's RGB code-value bias. - /// The reversible transform's RGB code-value range. - /// The largest reversible transform RGB code value. - 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; - } - - /// - /// Gets the red luma coefficient. - /// - public float Kr { get; } - - /// - /// Gets the green luma coefficient. - /// - public float Kg { get; } - - /// - /// Gets the blue luma coefficient. - /// - public float Kb { get; } - - /// - /// Gets the red contribution from the red-difference component. - /// - public float RedChromaScale { get; } - - /// - /// Gets the blue contribution from the blue-difference component. - /// - public float BlueChromaScale { get; } - - /// - /// Gets the red-difference subtraction from green. - /// - public float GreenRedChromaScale { get; } - - /// - /// Gets the blue-difference subtraction from green. - /// - public float GreenBlueChromaScale { get; } - - /// - /// Gets the signaled transfer characteristics. - /// - public ObuTransferCharacteristics TransferCharacteristics { get; } - - /// - /// Gets the constant-luminance chroma scales. - /// - public Av1ConstantLuminanceScales ConstantLuminanceScales { get; } - - /// - /// Gets the encoded luma bias. - /// - public float LumaBias { get; } - - /// - /// Gets the encoded luma range. - /// - public float LumaScale { get; } - - /// - /// Gets the encoded chroma midpoint. - /// - public float ChromaBias { get; } - - /// - /// Gets the encoded chroma range. - /// - public float ChromaScale { get; } - - /// - /// Gets the largest encoded sample value. - /// - public float EncodedSampleMaximum { get; } - - /// - /// Gets the reversible transform's RGB code-value bias. - /// - public float RgbBias { get; } - - /// - /// Gets the reversible transform's RGB code-value range. - /// - public float RgbScale { get; } - - /// - /// Gets the largest reversible transform RGB code value. - /// - public float RgbSampleMaximum { get; } -} - -/// -/// Stores the H.273 chroma normalization constants for constant-luminance conversion. -/// -internal readonly struct Av1ConstantLuminanceScales -{ - /// - /// Initializes a new instance of the struct. - /// - /// The signaled transfer characteristics. - /// The red luma coefficient. - /// The blue luma coefficient. - public Av1ConstantLuminanceScales(ObuTransferCharacteristics transferCharacteristics, float kr, float kb) - { - this.NegativeBlue = Av1TransferFunctions.ToGamma(transferCharacteristics, 1F - kb); - this.PositiveBlue = 1F - Av1TransferFunctions.ToGamma(transferCharacteristics, kb); - this.NegativeRed = Av1TransferFunctions.ToGamma(transferCharacteristics, 1F - kr); - this.PositiveRed = 1F - Av1TransferFunctions.ToGamma(transferCharacteristics, kr); - } - - /// - /// Gets the negative blue-difference scale. - /// - public float NegativeBlue { get; } - - /// - /// Gets the positive blue-difference scale. - /// - public float PositiveBlue { get; } - - /// - /// Gets the negative red-difference scale. - /// - public float NegativeRed { get; } - - /// - /// Gets the positive red-difference scale. - /// - public float PositiveRed { get; } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.RowConverters.cs b/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.RowConverters.cs index 515f05d088..c2e38c499a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.RowConverters.cs +++ b/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 where TPixel : unmanaged, IPixel where TSample : unmanaged - where TLoader : struct, ISampleLoader + where TLoader : struct, IHeifSampleLoader { /// /// The configuration used by bulk pixel conversion. @@ -59,7 +61,7 @@ internal static partial class Av1YuvConverter /// /// The frame-scoped color-model converter. /// - private readonly Av1ColorConverterBase colorConverter; + private readonly HeifColorConverterBase colorConverter; /// /// The signaled chroma sample position used for reconstruction. @@ -92,7 +94,7 @@ internal static partial class Av1YuvConverter Configuration configuration, Av1FrameBuffer frameBuffer, ImageFrame image, - Av1ColorConverterBase colorConverter) + HeifColorConverterBase colorConverter) { this.configuration = configuration; this.frameBuffer = frameBuffer; @@ -229,7 +231,7 @@ internal static partial class Av1YuvConverter private readonly struct RgbToYuvRowConverter where TPixel : unmanaged, IPixel where TSample : unmanaged - where TStorer : struct, ISampleStorer + where TStorer : struct, IHeifSampleStorer { /// /// The configuration used by bulk pixel conversion. @@ -264,7 +266,7 @@ internal static partial class Av1YuvConverter /// /// The frame-scoped color-model converter. /// - private readonly Av1ColorConverterBase colorConverter; + private readonly HeifColorConverterBase colorConverter; /// /// The largest value represented by the encoded AV1 bit depth. @@ -298,7 +300,7 @@ internal static partial class Av1YuvConverter Configuration configuration, Av1FrameBuffer frameBuffer, ImageFrame image, - Av1ColorConverterBase colorConverter, + HeifColorConverterBase colorConverter, float sampleMaximum) { this.configuration = configuration; @@ -383,8 +385,26 @@ internal static partial class Av1YuvConverter } else { - WriteSubsampledSamples(cbRow0, cbRow1, uDestination, chromaScale, chromaBias, this.sampleMaximum); - WriteSubsampledSamples(crRow0, crRow1, vDestination, chromaScale, chromaBias, this.sampleMaximum); + float secondRowWeight = hasSecondSourceRow ? 0.5F : 0F; + WriteSubsampledSamples( + cbRow0, + cbRow1, + uDestination, + true, + secondRowWeight, + chromaScale, + chromaBias, + this.sampleMaximum); + + WriteSubsampledSamples( + crRow0, + crRow1, + vDestination, + true, + secondRowWeight, + chromaScale, + chromaBias, + this.sampleMaximum); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs index 7ef0638a60..616d25d2a6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs +++ b/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(Configuration configuration, Av1FrameBuffer frameBuffer, ImageFrame image) where TPixel : unmanaged, IPixel { - 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 converter = new(configuration, frameBuffer, image, colorConverter); + YuvToRgbRowConverter converter = new(configuration, frameBuffer, image, colorConverter); using IMemoryOwner scratchOwner = configuration.MemoryAllocator.Allocate(converter.BufferLength); using IMemoryOwner proxyOwner = configuration.MemoryAllocator.Allocate(image.Width + 3); Span scratch = scratchOwner.GetSpan(); @@ -53,7 +54,7 @@ internal static partial class Av1YuvConverter } else { - YuvToRgbRowConverter converter = new(configuration, frameBuffer, image, colorConverter); + YuvToRgbRowConverter converter = new(configuration, frameBuffer, image, colorConverter); using IMemoryOwner owner = configuration.MemoryAllocator.Allocate(converter.BufferLength); Span scratch = owner.GetSpan(); for (int y = 0; y < image.Height; y++) @@ -73,16 +74,16 @@ internal static partial class Av1YuvConverter public static void ConvertFromRgb(Configuration configuration, ImageFrame image, Av1FrameBuffer frameBuffer) where TPixel : unmanaged, IPixel { - 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 converter = new(configuration, frameBuffer, image, colorConverter, sampleMaximum); + RgbToYuvRowConverter converter = new(configuration, frameBuffer, image, colorConverter, sampleMaximum); using IMemoryOwner componentOwner = configuration.MemoryAllocator.Allocate(converter.ComponentBufferLength); Span components = componentOwner.GetSpan(); for (int y = 0; y < iterationCount; y++) @@ -92,7 +93,7 @@ internal static partial class Av1YuvConverter } else { - RgbToYuvRowConverter converter = new(configuration, frameBuffer, image, colorConverter, sampleMaximum); + RgbToYuvRowConverter converter = new(configuration, frameBuffer, image, colorConverter, sampleMaximum); using IMemoryOwner packedOwner = configuration.MemoryAllocator.Allocate(image.Width); using IMemoryOwner componentOwner = configuration.MemoryAllocator.Allocate(converter.ComponentBufferLength); Span packed = packedOwner.GetSpan()[..image.Width]; @@ -110,288 +111,25 @@ internal static partial class Av1YuvConverter /// The AV1 frame containing the signaled color configuration. /// The resolved conversion mode. /// The resolved conversion parameters. - private static Av1ColorConversionParameters GetConversionParameters(Av1FrameBuffer frameBuffer, out Av1ColorConversionMode mode) + private static HeifColorConversionParameters GetConversionParameters(Av1FrameBuffer 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); - } - - /// - /// Computes the luma coefficients defined by the signaled H.273 primary chromaticities. - /// - /// The signaled color-primary code point. - /// The resulting red luma coefficient. - /// The resulting blue luma coefficient. - 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); } /// @@ -435,43 +173,4 @@ internal static partial class Av1YuvConverter upper = isOdd ? Math.Min(sample + 1, maximum) : sample; upperWeight = isOdd ? 2 : 0; } - - /// - /// Reads an 8-bit or 16-bit unsigned sample without introducing a separate conversion buffer. - /// - /// The encoded sample type. - /// The source samples. - /// The sample index. - /// The sample value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetSample(ReadOnlySpan source, int index) - where TSample : unmanaged - { - ref TSample sample = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), index); - return typeof(TSample) == typeof(byte) - ? Unsafe.As(ref sample) - : Unsafe.As(ref sample); - } - - /// - /// Rounds and clamps a conversion result to the encoded sample range. - /// - /// The encoded sample type. - /// The conversion result. - /// The largest encoded sample value. - /// The bounded sample. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TSample ToSample(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(ref result); - } - - ushort highBitDepthResult = (ushort)sample; - return Unsafe.As(ref highBitDepthResult); - } } diff --git a/src/ImageSharp/Formats/Heif/Color/HeifColorConversionParameters.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConversionParameters.cs new file mode 100644 index 0000000000..800ea90e9d --- /dev/null +++ b/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; + +/// +/// Stores the resolved H.273 values shared by every scalar and SIMD lane. +/// +internal readonly struct HeifColorConversionParameters +{ + /// + /// Initializes a new instance of the struct. + /// + /// The red luma coefficient. + /// The green luma coefficient. + /// The blue luma coefficient. + /// The signaled transfer characteristics. + /// The constant-luminance chroma scales. + /// The encoded luma bias. + /// The encoded luma range. + /// The encoded chroma midpoint. + /// The encoded chroma range. + /// The largest encoded luma sample value. + /// The largest encoded chroma sample value. + /// The reversible transform's RGB code-value bias. + /// The reversible transform's RGB code-value range. + /// The largest reversible transform RGB code value. + 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; + } + + /// + /// Gets the red luma coefficient. + /// + public float Kr { get; } + + /// + /// Gets the green luma coefficient. + /// + public float Kg { get; } + + /// + /// Gets the blue luma coefficient. + /// + public float Kb { get; } + + /// + /// Gets the red contribution from the red-difference component. + /// + public float RedChromaScale { get; } + + /// + /// Gets the blue contribution from the blue-difference component. + /// + public float BlueChromaScale { get; } + + /// + /// Gets the red-difference subtraction from green. + /// + public float GreenRedChromaScale { get; } + + /// + /// Gets the blue-difference subtraction from green. + /// + public float GreenBlueChromaScale { get; } + + /// + /// Gets the signaled transfer characteristics. + /// + public CicpTransferCharacteristics TransferCharacteristics { get; } + + /// + /// Gets the constant-luminance chroma scales. + /// + public HeifConstantLuminanceScales ConstantLuminanceScales { get; } + + /// + /// Gets the encoded luma bias. + /// + public float LumaBias { get; } + + /// + /// Gets the encoded luma range. + /// + public float LumaScale { get; } + + /// + /// Gets the encoded chroma midpoint. + /// + public float ChromaBias { get; } + + /// + /// Gets the encoded chroma range. + /// + public float ChromaScale { get; } + + /// + /// Gets the largest encoded luma sample value. + /// + public float LumaSampleMaximum { get; } + + /// + /// Gets the largest encoded chroma sample value. + /// + public float ChromaSampleMaximum { get; } + + /// + /// Gets the reversible transform's RGB code-value bias. + /// + public float RgbBias { get; } + + /// + /// Gets the reversible transform's RGB code-value range. + /// + public float RgbScale { get; } + + /// + /// Gets the largest reversible transform RGB code value. + /// + public float RgbSampleMaximum { get; } + + /// + /// Resolves the H.273 matrix coefficients and sample ranges shared by HEVC and AV1 image items. + /// + /// The H.273 color-primary code point. + /// The H.273 transfer-characteristic code point. + /// The H.273 matrix-coefficient code point. + /// Whether encoded components use their complete numeric range. + /// The encoded luma precision in bits. + /// The encoded chroma precision in bits. + /// Whether the image contains only luma samples. + /// Whether both chroma planes have luma resolution. + /// The resolved conversion operation. + /// The immutable values used by each scalar and SIMD conversion lane. + 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); + } + + /// + /// Computes the luma coefficients defined by an H.273 primary-chromaticity code point. + /// + /// The H.273 color-primary code point. + /// The resulting red luma coefficient. + /// The resulting blue luma coefficient. + 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; + } +} + +/// +/// Stores the H.273 chroma normalization constants for constant-luminance conversion. +/// +internal readonly struct HeifConstantLuminanceScales +{ + /// + /// Initializes a new instance of the struct. + /// + /// The signaled transfer characteristics. + /// The red luma coefficient. + /// The blue luma coefficient. + 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); + } + + /// + /// Gets the negative blue-difference scale. + /// + public float NegativeBlue { get; } + + /// + /// Gets the positive blue-difference scale. + /// + public float PositiveBlue { get; } + + /// + /// Gets the negative red-difference scale. + /// + public float NegativeRed { get; } + + /// + /// Gets the positive red-difference scale. + /// + public float PositiveRed { get; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.CoefficientOperator.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConverter.CoefficientOperator.cs similarity index 88% rename from src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.CoefficientOperator.cs rename to src/ImageSharp/Formats/Heif/Color/HeifColorConverter.CoefficientOperator.cs index 8d92ee7ad8..6219716a0d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.CoefficientOperator.cs +++ b/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 { /// /// Implements coefficient-based YCbCr conversion for scalar and SIMD lanes. /// - internal readonly struct Av1CoefficientColorOperator : IAv1ColorOperator + internal readonly struct HeifCoefficientColorOperator : IHeifColorOperator { /// public static bool ChromaUsesLumaRange => false; /// [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 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector128 y, ref Vector128 cb, ref Vector128 cr, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector128 y, ref Vector128 cb, ref Vector128 cr, in HeifColorConversionParameters parameters) { Vector128 r = Vector128.MultiplyAddEstimate(Vector128.Create(parameters.RedChromaScale), cr, y); Vector128 g = Vector128.MultiplyAddEstimate( @@ -49,7 +49,7 @@ internal abstract partial class Av1ColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector256 y, ref Vector256 cb, ref Vector256 cr, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector256 y, ref Vector256 cb, ref Vector256 cr, in HeifColorConversionParameters parameters) { Vector256 r = Vector256.MultiplyAddEstimate(Vector256.Create(parameters.RedChromaScale), cr, y); Vector256 g = Vector256.MultiplyAddEstimate( @@ -65,7 +65,7 @@ internal abstract partial class Av1ColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector512 y, ref Vector512 cb, ref Vector512 cr, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector512 y, ref Vector512 cb, ref Vector512 cr, in HeifColorConversionParameters parameters) { Vector512 r = Vector512.MultiplyAddEstimate(Vector512.Create(parameters.RedChromaScale), cr, y); Vector512 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 r, Vector128 g, Vector128 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector128 y, out Vector128 cb, out Vector128 cr) @@ -121,7 +121,7 @@ internal abstract partial class Av1ColorConverterBase Vector256 r, Vector256 g, Vector256 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector256 y, out Vector256 cb, out Vector256 cr) @@ -140,7 +140,7 @@ internal abstract partial class Av1ColorConverterBase Vector512 r, Vector512 g, Vector512 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector512 y, out Vector512 cb, out Vector512 cr) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ConstantLuminanceOperator.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConverter.ConstantLuminanceOperator.cs similarity index 66% rename from src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ConstantLuminanceOperator.cs rename to src/ImageSharp/Formats/Heif/Color/HeifColorConverter.ConstantLuminanceOperator.cs index 990113ead3..3fe0f47715 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ConstantLuminanceOperator.cs +++ b/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 { /// /// Implements H.273 constant-luminance conversion for scalar and SIMD lanes. /// - internal readonly struct Av1ConstantLuminanceColorOperator : IAv1ColorOperator + internal readonly struct HeifConstantLuminanceColorOperator : IHeifColorOperator { /// public static bool ChromaUsesLumaRange => false; /// [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; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector128 y, ref Vector128 cb, ref Vector128 cr, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector128 y, ref Vector128 cb, ref Vector128 cr, in HeifColorConversionParameters parameters) { - Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; + HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales; Vector128 blueScale = Vector128.ConditionalSelect( Vector128.LessThanOrEqual(cb, Vector128.Zero), Vector128.Create(scales.NegativeBlue), @@ -50,23 +50,23 @@ internal abstract partial class Av1ColorConverterBase Vector128.Create(scales.PositiveRed)); Vector128 nonlinearBlue = Vector128.MultiplyAddEstimate(Vector128.Create(2F) * blueScale, cb, y); Vector128 nonlinearRed = Vector128.MultiplyAddEstimate(Vector128.Create(2F) * redScale, cr, y); - Vector128 linearY = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, y); - Vector128 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue); - Vector128 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed); + Vector128 linearY = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, y); + Vector128 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue); + Vector128 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed); Vector128 linearGreen = ( linearY - (Vector128.Create(parameters.Kr) * linearRed) - (Vector128.Create(parameters.Kb) * linearBlue)) / Vector128.Create(parameters.Kg); y = nonlinearRed; - cb = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); + cb = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); cr = nonlinearBlue; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector256 y, ref Vector256 cb, ref Vector256 cr, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector256 y, ref Vector256 cb, ref Vector256 cr, in HeifColorConversionParameters parameters) { - Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; + HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales; Vector256 blueScale = Vector256.ConditionalSelect( Vector256.LessThanOrEqual(cb, Vector256.Zero), Vector256.Create(scales.NegativeBlue), @@ -77,23 +77,23 @@ internal abstract partial class Av1ColorConverterBase Vector256.Create(scales.PositiveRed)); Vector256 nonlinearBlue = Vector256.MultiplyAddEstimate(Vector256.Create(2F) * blueScale, cb, y); Vector256 nonlinearRed = Vector256.MultiplyAddEstimate(Vector256.Create(2F) * redScale, cr, y); - Vector256 linearY = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, y); - Vector256 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue); - Vector256 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed); + Vector256 linearY = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, y); + Vector256 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue); + Vector256 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed); Vector256 linearGreen = ( linearY - (Vector256.Create(parameters.Kr) * linearRed) - (Vector256.Create(parameters.Kb) * linearBlue)) / Vector256.Create(parameters.Kg); y = nonlinearRed; - cb = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); + cb = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); cr = nonlinearBlue; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector512 y, ref Vector512 cb, ref Vector512 cr, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector512 y, ref Vector512 cb, ref Vector512 cr, in HeifColorConversionParameters parameters) { - Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; + HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales; Vector512 blueScale = Vector512.ConditionalSelect( Vector512.LessThanOrEqual(cb, Vector512.Zero), Vector512.Create(scales.NegativeBlue), @@ -104,15 +104,15 @@ internal abstract partial class Av1ColorConverterBase Vector512.Create(scales.PositiveRed)); Vector512 nonlinearBlue = Vector512.MultiplyAddEstimate(Vector512.Create(2F) * blueScale, cb, y); Vector512 nonlinearRed = Vector512.MultiplyAddEstimate(Vector512.Create(2F) * redScale, cr, y); - Vector512 linearY = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, y); - Vector512 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue); - Vector512 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed); + Vector512 linearY = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, y); + Vector512 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue); + Vector512 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed); Vector512 linearGreen = ( linearY - (Vector512.Create(parameters.Kr) * linearRed) - (Vector512.Create(parameters.Kb) * linearBlue)) / Vector512.Create(parameters.Kg); y = nonlinearRed; - cb = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); + 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 r, Vector128 g, Vector128 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector128 y, out Vector128 cb, out Vector128 cr) { - Vector128 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, r); - Vector128 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, g); - Vector128 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, b); + Vector128 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, r); + Vector128 linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, g); + Vector128 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, b); Vector128 linearY = Vector128.MultiplyAddEstimate( Vector128.Create(parameters.Kr), linearRed, Vector128.MultiplyAddEstimate(Vector128.Create(parameters.Kg), linearGreen, Vector128.Create(parameters.Kb) * linearBlue)); - y = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY); + y = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY); Vector128 blueDifference = b - y; Vector128 redDifference = r - y; - Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; + HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales; Vector128 blueScale = Vector128.ConditionalSelect( Vector128.LessThanOrEqual(blueDifference, Vector128.Zero), Vector128.Create(scales.NegativeBlue), @@ -182,22 +182,22 @@ internal abstract partial class Av1ColorConverterBase Vector256 r, Vector256 g, Vector256 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector256 y, out Vector256 cb, out Vector256 cr) { - Vector256 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, r); - Vector256 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, g); - Vector256 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, b); + Vector256 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, r); + Vector256 linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, g); + Vector256 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, b); Vector256 linearY = Vector256.MultiplyAddEstimate( Vector256.Create(parameters.Kr), linearRed, Vector256.MultiplyAddEstimate(Vector256.Create(parameters.Kg), linearGreen, Vector256.Create(parameters.Kb) * linearBlue)); - y = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY); + y = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY); Vector256 blueDifference = b - y; Vector256 redDifference = r - y; - Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; + HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales; Vector256 blueScale = Vector256.ConditionalSelect( Vector256.LessThanOrEqual(blueDifference, Vector256.Zero), Vector256.Create(scales.NegativeBlue), @@ -217,22 +217,22 @@ internal abstract partial class Av1ColorConverterBase Vector512 r, Vector512 g, Vector512 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector512 y, out Vector512 cb, out Vector512 cr) { - Vector512 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, r); - Vector512 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, g); - Vector512 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, b); + Vector512 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, r); + Vector512 linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, g); + Vector512 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, b); Vector512 linearY = Vector512.MultiplyAddEstimate( Vector512.Create(parameters.Kr), linearRed, Vector512.MultiplyAddEstimate(Vector512.Create(parameters.Kg), linearGreen, Vector512.Create(parameters.Kb) * linearBlue)); - y = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY); + y = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY); Vector512 blueDifference = b - y; Vector512 redDifference = r - y; - Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; + HeifConstantLuminanceScales scales = parameters.ConstantLuminanceScales; Vector512 blueScale = Vector512.ConditionalSelect( Vector512.LessThanOrEqual(blueDifference, Vector512.Zero), Vector512.Create(scales.NegativeBlue), diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ICtCpOperator.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConverter.ICtCpOperator.cs similarity index 74% rename from src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ICtCpOperator.cs rename to src/ImageSharp/Formats/Heif/Color/HeifColorConverter.ICtCpOperator.cs index 146623057d..325eaea586 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ICtCpOperator.cs +++ b/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 { /// /// Implements BT.2100 ICtCp conversion for scalar and SIMD lanes. /// - internal readonly struct Av1ICtCpColorOperator : IAv1ColorOperator + internal readonly struct HeifICtCpColorOperator : IHeifColorOperator { /// /// The PQ Ct contribution to nonlinear L. @@ -209,9 +209,9 @@ internal abstract partial class Av1ColorConverterBase /// [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); } /// @@ -238,9 +238,9 @@ internal abstract partial class Av1ColorConverterBase ref Vector128 intensity, ref Vector128 ct, ref Vector128 cp, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { - bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg; + bool isHlg = parameters.TransferCharacteristics == CicpTransferCharacteristics.AribStdB67; Vector128 ctContribution = Vector128.Create(isHlg ? HlgCtToL : PqCtToL) * ct; Vector128 cpContribution = Vector128.Create(isHlg ? HlgCpToL : PqCpToL) * cp; Vector128 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 linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); - Vector128 linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); - Vector128 linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); + Vector128 linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); + Vector128 linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); + Vector128 linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); Vector128 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); } /// @@ -276,9 +276,9 @@ internal abstract partial class Av1ColorConverterBase ref Vector256 intensity, ref Vector256 ct, ref Vector256 cp, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { - bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg; + bool isHlg = parameters.TransferCharacteristics == CicpTransferCharacteristics.AribStdB67; Vector256 ctContribution = Vector256.Create(isHlg ? HlgCtToL : PqCtToL) * ct; Vector256 cpContribution = Vector256.Create(isHlg ? HlgCpToL : PqCpToL) * cp; Vector256 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 linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); - Vector256 linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); - Vector256 linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); + Vector256 linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); + Vector256 linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); + Vector256 linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); Vector256 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); } /// @@ -314,9 +314,9 @@ internal abstract partial class Av1ColorConverterBase ref Vector512 intensity, ref Vector512 ct, ref Vector512 cp, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { - bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg; + bool isHlg = parameters.TransferCharacteristics == CicpTransferCharacteristics.AribStdB67; Vector512 ctContribution = Vector512.Create(isHlg ? HlgCtToL : PqCtToL) * ct; Vector512 cpContribution = Vector512.Create(isHlg ? HlgCpToL : PqCpToL) * cp; Vector512 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 linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); - Vector512 linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); - Vector512 linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); + Vector512 linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); + Vector512 linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); + Vector512 linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); Vector512 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); } /// @@ -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 red, Vector128 green, Vector128 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector128 intensity, out Vector128 ct, out Vector128 cp) { - Vector128 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red); - Vector128 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green); - Vector128 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); + Vector128 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red); + Vector128 linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green); + Vector128 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); Vector128 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 nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); - Vector128 nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); - Vector128 nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); + Vector128 nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); + Vector128 nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); + Vector128 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 red, Vector256 green, Vector256 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector256 intensity, out Vector256 ct, out Vector256 cp) { - Vector256 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red); - Vector256 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green); - Vector256 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); + Vector256 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red); + Vector256 linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green); + Vector256 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); Vector256 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 nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); - Vector256 nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); - Vector256 nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); + Vector256 nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); + Vector256 nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); + Vector256 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 red, Vector512 green, Vector512 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector512 intensity, out Vector512 ct, out Vector512 cp) { - Vector512 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red); - Vector512 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green); - Vector512 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); + Vector512 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red); + Vector512 linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green); + Vector512 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); Vector512 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 nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); - Vector512 nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); - Vector512 nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); + Vector512 nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); + Vector512 nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); + Vector512 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; diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.IdentityOperator.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConverter.IdentityOperator.cs similarity index 85% rename from src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.IdentityOperator.cs rename to src/ImageSharp/Formats/Heif/Color/HeifColorConverter.IdentityOperator.cs index f3bf16cc02..147d0018f9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.IdentityOperator.cs +++ b/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 { /// /// Implements direct G, B, and R plane mapping for scalar and SIMD lanes. /// - internal readonly struct Av1IdentityColorOperator : IAv1ColorOperator + internal readonly struct HeifIdentityColorOperator : IHeifColorOperator { /// public static bool ChromaUsesLumaRange => true; /// [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 green, ref Vector128 blue, ref Vector128 red, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { Vector128 g = green; green = red; @@ -48,7 +48,7 @@ internal abstract partial class Av1ColorConverterBase ref Vector256 green, ref Vector256 blue, ref Vector256 red, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { Vector256 g = green; green = red; @@ -62,7 +62,7 @@ internal abstract partial class Av1ColorConverterBase ref Vector512 green, ref Vector512 blue, ref Vector512 red, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { Vector512 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 red, Vector128 green, Vector128 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector128 component0, out Vector128 component1, out Vector128 component2) @@ -109,7 +109,7 @@ internal abstract partial class Av1ColorConverterBase Vector256 red, Vector256 green, Vector256 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector256 component0, out Vector256 component1, out Vector256 component2) @@ -125,7 +125,7 @@ internal abstract partial class Av1ColorConverterBase Vector512 red, Vector512 green, Vector512 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector512 component0, out Vector512 component1, out Vector512 component2) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.IptC2Operator.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConverter.IptC2Operator.cs similarity index 75% rename from src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.IptC2Operator.cs rename to src/ImageSharp/Formats/Heif/Color/HeifColorConverter.IptC2Operator.cs index 422c1bcf92..ab90e80183 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.IptC2Operator.cs +++ b/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 { /// /// Implements IPT-C2 conversion for scalar and SIMD lanes. /// - internal readonly struct Av1IptC2ColorOperator : IAv1ColorOperator + internal readonly struct HeifIptC2ColorOperator : IHeifColorOperator { /// /// The linear red contribution to L. @@ -183,23 +183,23 @@ internal abstract partial class Av1ColorConverterBase /// [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); } /// @@ -208,7 +208,7 @@ internal abstract partial class Av1ColorConverterBase ref Vector128 intensity, ref Vector128 protan, ref Vector128 tritan, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { Vector128 nonlinearL = Vector128.MultiplyAddEstimate( Vector128.Create(TritanToL), @@ -225,9 +225,9 @@ internal abstract partial class Av1ColorConverterBase tritan, Vector128.MultiplyAddEstimate(Vector128.Create(ProtanToS), protan, intensity)); - Vector128 linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); - Vector128 linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); - Vector128 linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); + Vector128 linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); + Vector128 linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); + Vector128 linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); Vector128 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 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); } /// @@ -248,7 +248,7 @@ internal abstract partial class Av1ColorConverterBase ref Vector256 intensity, ref Vector256 protan, ref Vector256 tritan, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { Vector256 nonlinearL = Vector256.MultiplyAddEstimate( Vector256.Create(TritanToL), @@ -265,9 +265,9 @@ internal abstract partial class Av1ColorConverterBase tritan, Vector256.MultiplyAddEstimate(Vector256.Create(ProtanToS), protan, intensity)); - Vector256 linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); - Vector256 linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); - Vector256 linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); + Vector256 linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); + Vector256 linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); + Vector256 linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); Vector256 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 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); } /// @@ -288,7 +288,7 @@ internal abstract partial class Av1ColorConverterBase ref Vector512 intensity, ref Vector512 protan, ref Vector512 tritan, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { Vector512 nonlinearL = Vector512.MultiplyAddEstimate( Vector512.Create(TritanToL), @@ -305,9 +305,9 @@ internal abstract partial class Av1ColorConverterBase tritan, Vector512.MultiplyAddEstimate(Vector512.Create(ProtanToS), protan, intensity)); - Vector512 linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); - Vector512 linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); - Vector512 linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); + Vector512 linearL = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); + Vector512 linearM = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); + Vector512 linearS = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); Vector512 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 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); } /// @@ -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 red, Vector128 green, Vector128 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector128 intensity, out Vector128 protan, out Vector128 tritan) { - Vector128 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red); - Vector128 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green); - Vector128 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); + Vector128 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red); + Vector128 linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green); + Vector128 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); Vector128 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 nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); - Vector128 nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); - Vector128 nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); + Vector128 nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); + Vector128 nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); + Vector128 nonlinearS = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); intensity = Vector128.MultiplyAddEstimate( Vector128.Create(SToIntensity), nonlinearS, @@ -409,14 +409,14 @@ internal abstract partial class Av1ColorConverterBase Vector256 red, Vector256 green, Vector256 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector256 intensity, out Vector256 protan, out Vector256 tritan) { - Vector256 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red); - Vector256 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green); - Vector256 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); + Vector256 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red); + Vector256 linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green); + Vector256 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); Vector256 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 nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); - Vector256 nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); - Vector256 nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); + Vector256 nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); + Vector256 nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); + Vector256 nonlinearS = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); intensity = Vector256.MultiplyAddEstimate( Vector256.Create(SToIntensity), nonlinearS, @@ -457,14 +457,14 @@ internal abstract partial class Av1ColorConverterBase Vector512 red, Vector512 green, Vector512 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector512 intensity, out Vector512 protan, out Vector512 tritan) { - Vector512 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red); - Vector512 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green); - Vector512 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); + Vector512 linearRed = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, red); + Vector512 linearGreen = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, green); + Vector512 linearBlue = HeifTransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); Vector512 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 nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); - Vector512 nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); - Vector512 nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); + Vector512 nonlinearL = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); + Vector512 nonlinearM = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); + Vector512 nonlinearS = HeifTransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); intensity = Vector512.MultiplyAddEstimate( Vector512.Create(SToIntensity), nonlinearS, diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.Operator.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Operator.cs similarity index 93% rename from src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.Operator.cs rename to src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Operator.cs index 5b29b067d1..6ead3b838d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.Operator.cs +++ b/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; /// /// Provides the static operator contract and SIMD traversal used by AV1 color converters. /// -internal abstract partial class Av1ColorConverterBase +internal abstract partial class HeifColorConverterBase { /// /// Defines color-model arithmetic for scalar and SIMD lanes in both conversion directions. /// - internal interface IAv1ColorOperator + internal interface IHeifColorOperator { /// /// 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); /// /// Converts four normalized AV1 samples to RGB. @@ -46,7 +46,7 @@ internal abstract partial class Av1ColorConverterBase ref Vector128 component0, ref Vector128 component1, ref Vector128 component2, - in Av1ColorConversionParameters parameters); + in HeifColorConversionParameters parameters); /// /// Converts eight normalized AV1 samples to RGB. @@ -59,7 +59,7 @@ internal abstract partial class Av1ColorConverterBase ref Vector256 component0, ref Vector256 component1, ref Vector256 component2, - in Av1ColorConversionParameters parameters); + in HeifColorConversionParameters parameters); /// /// Converts sixteen normalized AV1 samples to RGB. @@ -72,10 +72,10 @@ internal abstract partial class Av1ColorConverterBase ref Vector512 component0, ref Vector512 component1, ref Vector512 component2, - in Av1ColorConversionParameters parameters); + in HeifColorConversionParameters parameters); /// - /// Converts one normalized RGB sample to AV1 components. + /// Converts one normalized RGB sample to encoded components. /// /// The normalized red component. /// The normalized green component. @@ -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); /// - /// Converts four normalized RGB samples to AV1 components. + /// Converts four normalized RGB samples to encoded components. /// /// The normalized red lanes. /// The normalized green lanes. @@ -107,13 +107,13 @@ internal abstract partial class Av1ColorConverterBase Vector128 red, Vector128 green, Vector128 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector128 component0, out Vector128 component1, out Vector128 component2); /// - /// Converts eight normalized RGB samples to AV1 components. + /// Converts eight normalized RGB samples to encoded components. /// /// The normalized red lanes. /// The normalized green lanes. @@ -126,13 +126,13 @@ internal abstract partial class Av1ColorConverterBase Vector256 red, Vector256 green, Vector256 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector256 component0, out Vector256 component1, out Vector256 component2); /// - /// Converts sixteen normalized RGB samples to AV1 components. + /// Converts sixteen normalized RGB samples to encoded components. /// /// The normalized red lanes. /// The normalized green lanes. @@ -145,25 +145,25 @@ internal abstract partial class Av1ColorConverterBase Vector512 red, Vector512 green, Vector512 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector512 component0, out Vector512 component1, out Vector512 component2); } /// - /// 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. /// /// The color-model-specific arithmetic. - internal sealed class Av1ColorConverter : Av1ColorConverterBase - where TOperator : struct, IAv1ColorOperator + internal sealed class HeifColorConverter : HeifColorConverterBase + where TOperator : struct, IHeifColorOperator { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The resolved H.273 conversion parameters. /// Whether the frame contains only luma samples. - 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 /// public override void ConvertToRgbInPlace(Span component0, Span component1, Span 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 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. diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.Simd.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Samples.cs similarity index 83% rename from src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.Simd.cs rename to src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Samples.cs index 64115a22bd..ad807ae77e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.Simd.cs +++ b/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; /// -/// 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. /// -internal static partial class Av1YuvConverter +internal abstract partial class HeifColorConverterBase { /// - /// Defines the SIMD widening operations for one reconstructed AV1 sample type. + /// The largest value represented by a 16-bit packed RGB component. + /// + private const float UShortMaximum = ushort.MaxValue; + + /// + /// Defines the SIMD widening operations for one reconstructed HEIF sample type. /// /// The reconstructed sample type. - private interface ISampleLoader + public interface IHeifSampleLoader where TSample : unmanaged { /// @@ -44,10 +49,10 @@ internal static partial class Av1YuvConverter } /// - /// 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. /// /// The encoded sample type. - private interface ISampleStorer + public interface IHeifSampleStorer where TSample : unmanaged { /// @@ -79,9 +84,9 @@ internal static partial class Av1YuvConverter /// The widening operations for the sample type. /// The reconstructed samples. /// The destination component row. - private static void ConvertSamplesToFloat(ReadOnlySpan source, Span destination) + public static void ConvertSamplesToFloat(ReadOnlySpan source, Span destination) where TSample : unmanaged - where TLoader : struct, ISampleLoader + where TLoader : struct, IHeifSampleLoader { ref TSample sourceBase = ref MemoryMarshal.GetReference(source); ref float destinationBase = ref MemoryMarshal.GetReference(destination); @@ -139,7 +144,7 @@ internal static partial class Av1YuvConverter /// The reconstructed full-width chroma row. /// The first pooled chroma scratch row. /// The second pooled chroma scratch row. - private static void ReconstructChromaRow( + public static void ReconstructChromaRow( ReadOnlySpan row0, ReadOnlySpan row1, int y1Weight, @@ -149,7 +154,7 @@ internal static partial class Av1YuvConverter Span scratch0, Span scratch1) where TSample : unmanaged - where TLoader : struct, ISampleLoader + where TLoader : struct, IHeifSampleLoader { int sourceLength = subX == 0 ? destination.Length : (destination.Length + 1) >> 1; Span top = scratch0[..sourceLength]; @@ -172,7 +177,7 @@ internal static partial class Av1YuvConverter } /// - /// Interpolates two chroma rows in place using AV1 quarter-sample weights. + /// Interpolates two chroma rows in place using quarter-sample weights. /// /// The upper row, replaced by the interpolated values. /// The lower row. @@ -234,7 +239,7 @@ internal static partial class Av1YuvConverter } /// - /// 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. /// /// The subsampled chroma values. /// The full-width chroma values. @@ -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 /// The destination red components. /// The destination green components. /// The destination blue components. - private static void DeinterleaveRgb48(ReadOnlySpan source, Span red, Span green, Span blue) + public static void DeinterleaveRgb48(ReadOnlySpan source, Span red, Span green, Span blue) { ref float redBase = ref MemoryMarshal.GetReference(red); ref float greenBase = ref MemoryMarshal.GetReference(green); @@ -445,9 +450,9 @@ internal static partial class Av1YuvConverter /// The encoded range scale. /// The encoded range bias. /// The largest encoded sample value. - private static void WriteSamples(ReadOnlySpan source, Span destination, float scale, float bias, float maximum) + public static void WriteSamples(ReadOnlySpan source, Span destination, float scale, float bias, float maximum) where TSample : unmanaged - where TStorer : struct, ISampleStorer + where TStorer : struct, IHeifSampleStorer { ref float sourceBase = ref MemoryMarshal.GetReference(source); ref TSample destinationBase = ref MemoryMarshal.GetReference(destination); @@ -491,113 +496,151 @@ internal static partial class Av1YuvConverter } /// - /// Averages one or two planar rows into horizontally subsampled encoded samples. + /// Filters one or two planar rows into horizontally subsampled encoded samples. /// /// The encoded sample type. /// The narrowing and storage operations for the sample type. /// The first normalized source row. /// The optional second normalized source row. /// The encoded subsampled destination row. + /// Whether each output sample is centered between two horizontal source samples. + /// The contribution of the second row, in the inclusive range zero through one. /// The encoded range scale. /// The encoded range bias. /// The largest encoded sample value. - private static void WriteSubsampledSamples( + public static void WriteSubsampledSamples( ReadOnlySpan row0, ReadOnlySpan row1, Span destination, + bool isCenteredX, + float row1Weight, float scale, float bias, float maximum) where TSample : unmanaged - where TStorer : struct, ISampleStorer + where TStorer : struct, IHeifSampleStorer { 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 evenOdd = Vector512.Create(0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15); + Vector512 row0WeightVector = Vector512.Create(row0Weight); + Vector512 row1WeightVector = Vector512.Create(effectiveRow1Weight); int oneVectorFromEnd = length - Vector512.Count; for (; i <= oneVectorFromEnd; i += Vector512.Count) { - Vector512 sum = Unsafe.As>(ref Unsafe.Add(ref row0Base, i)); + Vector512 filtered = Unsafe.As>(ref Unsafe.Add(ref row0Base, i)) * row0WeightVector; if (hasSecondRow) { - sum += Unsafe.As>(ref Unsafe.Add(ref row1Base, i)); + filtered = Vector512.MultiplyAddEstimate( + Unsafe.As>(ref Unsafe.Add(ref row1Base, i)), + row1WeightVector, + filtered); } - Vector512 shuffled = Vector512.Shuffle(sum, evenOdd); - Vector256 average = (shuffled.GetLower() + shuffled.GetUpper()) * Vector256.Create(averageScale); - TStorer.Store(ScaleBiasRoundAndClampToInt32(average, scale, bias, maximum), ref Unsafe.Add(ref destinationBase, i >> 1)); + Vector512 shuffled = Vector512.Shuffle(filtered, evenOdd); + Vector256 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 evenOdd = Vector256.Create(0, 2, 4, 6, 1, 3, 5, 7); + Vector256 row0WeightVector = Vector256.Create(row0Weight); + Vector256 row1WeightVector = Vector256.Create(effectiveRow1Weight); int oneVectorFromEnd = length - Vector256.Count; for (; i <= oneVectorFromEnd; i += Vector256.Count) { - Vector256 sum = Unsafe.As>(ref Unsafe.Add(ref row0Base, i)); + Vector256 filtered = Unsafe.As>(ref Unsafe.Add(ref row0Base, i)) * row0WeightVector; if (hasSecondRow) { - sum += Unsafe.As>(ref Unsafe.Add(ref row1Base, i)); + filtered = Vector256.MultiplyAddEstimate( + Unsafe.As>(ref Unsafe.Add(ref row1Base, i)), + row1WeightVector, + filtered); } - Vector256 shuffled = Vector256.Shuffle(sum, evenOdd); - Vector128 average = (shuffled.GetLower() + shuffled.GetUpper()) * Vector128.Create(averageScale); - TStorer.Store(ScaleBiasRoundAndClampToInt32(average, scale, bias, maximum), ref Unsafe.Add(ref destinationBase, i >> 1)); + Vector256 shuffled = Vector256.Shuffle(filtered, evenOdd); + Vector128 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 evenOdd = Vector128.Create(0, 2, 1, 3); + Vector128 row0WeightVector = Vector128.Create(row0Weight); + Vector128 row1WeightVector = Vector128.Create(effectiveRow1Weight); int twoVectorsFromEnd = length - (Vector128.Count * 2); for (; i <= twoVectorsFromEnd; i += Vector128.Count * 2) { - Vector128 sum0 = Unsafe.As>(ref Unsafe.Add(ref row0Base, i)); - Vector128 sum1 = Unsafe.As>(ref Unsafe.Add(ref row0Base, i + Vector128.Count)); + Vector128 filtered0 = Unsafe.As>(ref Unsafe.Add(ref row0Base, i)) * row0WeightVector; + Vector128 filtered1 = Unsafe.As>(ref Unsafe.Add(ref row0Base, i + Vector128.Count)) * row0WeightVector; if (hasSecondRow) { - sum0 += Unsafe.As>(ref Unsafe.Add(ref row1Base, i)); - sum1 += Unsafe.As>(ref Unsafe.Add(ref row1Base, i + Vector128.Count)); + filtered0 = Vector128.MultiplyAddEstimate( + Unsafe.As>(ref Unsafe.Add(ref row1Base, i)), + row1WeightVector, + filtered0); + + filtered1 = Vector128.MultiplyAddEstimate( + Unsafe.As>(ref Unsafe.Add(ref row1Base, i + Vector128.Count)), + row1WeightVector, + filtered1); } - Vector128 shuffled0 = Vector128.Shuffle(sum0, evenOdd); - Vector128 shuffled1 = Vector128.Shuffle(sum1, evenOdd); - Vector64 average0 = (shuffled0.GetLower() + shuffled0.GetUpper()) * Vector64.Create(averageScale); - Vector64 average1 = (shuffled1.GetLower() + shuffled1.GetUpper()) * Vector64.Create(averageScale); - Vector128 average = Vector128.Create(average0, average1); - TStorer.Store(ScaleBiasRoundAndClampToInt32(average, scale, bias, maximum), ref Unsafe.Add(ref destinationBase, i >> 1)); + Vector128 shuffled0 = Vector128.Shuffle(filtered0, evenOdd); + Vector128 shuffled1 = Vector128.Shuffle(filtered1, evenOdd); + Vector64 samples0 = isCenteredX + ? (shuffled0.GetLower() + shuffled0.GetUpper()) * Vector64.Create(0.5F) + : shuffled0.GetLower(); + + Vector64 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(((sum / sampleCount) * scale) + bias, maximum); + Unsafe.Add(ref destinationBase, i >> 1) = ToSample((sample * scale) + bias, maximum); } } @@ -608,7 +651,7 @@ internal static partial class Av1YuvConverter /// The normalized green components. /// The normalized blue components. /// The destination pixels. - private static void PackRgba64(ReadOnlySpan red, ReadOnlySpan green, ReadOnlySpan blue, Span destination) + public static void PackRgba64(ReadOnlySpan red, ReadOnlySpan green, ReadOnlySpan blue, Span destination) { ref float redBase = ref MemoryMarshal.GetReference(red); ref float greenBase = ref MemoryMarshal.GetReference(green); @@ -667,6 +710,45 @@ internal static partial class Av1YuvConverter } } + /// + /// Reads an eight-bit or 16-bit unsigned sample without an intermediate conversion buffer. + /// + /// The encoded sample type. + /// The source samples. + /// The zero-based sample index. + /// The sample value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float GetSample(ReadOnlySpan source, int index) + where TSample : unmanaged + { + ref TSample sample = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), index); + return typeof(TSample) == typeof(byte) + ? Unsafe.As(ref sample) + : Unsafe.As(ref sample); + } + + /// + /// Rounds and clamps a conversion result to the encoded sample range. + /// + /// The encoded sample type. + /// The conversion result. + /// The largest encoded sample value. + /// The bounded sample. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TSample ToSample(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(ref result); + } + + ushort highBitDepthResult = (ushort)sample; + return Unsafe.As(ref highBitDepthResult); + } + /// /// Applies the encoded component range and converts four lanes to bounded integer samples. /// @@ -775,7 +857,7 @@ internal static partial class Av1YuvConverter /// /// Widens reconstructed eight-bit samples using exact unsigned conversions. /// - private readonly struct ByteSampleLoader : ISampleLoader + public readonly struct HeifByteSampleLoader : IHeifSampleLoader { /// public static Vector128 LoadVector128(ref byte source) @@ -808,7 +890,7 @@ internal static partial class Av1YuvConverter /// /// Widens reconstructed high-bit-depth samples using exact unsigned conversions. /// - private readonly struct UShortSampleLoader : ISampleLoader + public readonly struct HeifUShortSampleLoader : IHeifSampleLoader { /// public static Vector128 LoadVector128(ref ushort source) @@ -838,7 +920,7 @@ internal static partial class Av1YuvConverter /// /// Narrows encoded integer lanes to eight-bit samples. /// - private readonly struct ByteSampleStorer : ISampleStorer + public readonly struct HeifByteSampleStorer : IHeifSampleStorer { /// public static void Store(Vector128 source, ref byte destination) @@ -868,7 +950,7 @@ internal static partial class Av1YuvConverter /// /// Narrows encoded integer lanes to unsigned 16-bit samples. /// - private readonly struct UShortSampleStorer : ISampleStorer + public readonly struct HeifUShortSampleStorer : IHeifSampleStorer { /// public static void Store(Vector128 source, ref ushort destination) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.Smpte2085Operator.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Smpte2085Operator.cs similarity index 87% rename from src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.Smpte2085Operator.cs rename to src/ImageSharp/Formats/Heif/Color/HeifColorConverter.Smpte2085Operator.cs index 7facafd7f2..8528a54d18 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.Smpte2085Operator.cs +++ b/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 { /// /// Implements SMPTE ST 2085 YDzDx conversion for scalar and SIMD lanes. /// - internal readonly struct Av1Smpte2085ColorOperator : IAv1ColorOperator + internal readonly struct HeifSmpte2085ColorOperator : IHeifColorOperator { /// /// The SMPTE ST 2085 blue primary normalization factor. @@ -28,7 +28,7 @@ internal abstract partial class Av1ColorConverterBase /// [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 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector128 y, ref Vector128 dz, ref Vector128 dx, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector128 y, ref Vector128 dz, ref Vector128 dx, in HeifColorConversionParameters parameters) { Vector128 g = y; Vector128 b = Vector128.MultiplyAddEstimate(Vector128.Create(2F), dz, y) / Vector128.Create(BlueNormalization); @@ -53,7 +53,7 @@ internal abstract partial class Av1ColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector256 y, ref Vector256 dz, ref Vector256 dx, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector256 y, ref Vector256 dz, ref Vector256 dx, in HeifColorConversionParameters parameters) { Vector256 g = y; Vector256 b = Vector256.MultiplyAddEstimate(Vector256.Create(2F), dz, y) / Vector256.Create(BlueNormalization); @@ -65,7 +65,7 @@ internal abstract partial class Av1ColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector512 y, ref Vector512 dz, ref Vector512 dx, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector512 y, ref Vector512 dz, ref Vector512 dx, in HeifColorConversionParameters parameters) { Vector512 g = y; Vector512 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 r, Vector128 g, Vector128 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector128 y, out Vector128 dz, out Vector128 dx) @@ -114,7 +114,7 @@ internal abstract partial class Av1ColorConverterBase Vector256 r, Vector256 g, Vector256 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector256 y, out Vector256 dz, out Vector256 dx) @@ -130,7 +130,7 @@ internal abstract partial class Av1ColorConverterBase Vector512 r, Vector512 g, Vector512 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector512 y, out Vector512 dz, out Vector512 dx) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.YCgCoOperator.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConverter.YCgCoOperator.cs similarity index 85% rename from src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.YCgCoOperator.cs rename to src/ImageSharp/Formats/Heif/Color/HeifColorConverter.YCgCoOperator.cs index 8a62b72a76..a6f472d779 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.YCgCoOperator.cs +++ b/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 { /// /// Implements YCgCo conversion for scalar and SIMD lanes. /// - internal readonly struct Av1YCgCoColorOperator : IAv1ColorOperator + internal readonly struct HeifYCgCoColorOperator : IHeifColorOperator { /// public static bool ChromaUsesLumaRange => false; /// [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 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector128 y, ref Vector128 cg, ref Vector128 co, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector128 y, ref Vector128 cg, ref Vector128 co, in HeifColorConversionParameters parameters) { Vector128 temporary = y - cg; Vector128 r = temporary + co; @@ -45,7 +45,7 @@ internal abstract partial class Av1ColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector256 y, ref Vector256 cg, ref Vector256 co, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector256 y, ref Vector256 cg, ref Vector256 co, in HeifColorConversionParameters parameters) { Vector256 temporary = y - cg; Vector256 r = temporary + co; @@ -58,7 +58,7 @@ internal abstract partial class Av1ColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb(ref Vector512 y, ref Vector512 cg, ref Vector512 co, in Av1ColorConversionParameters parameters) + public static void ConvertToRgb(ref Vector512 y, ref Vector512 cg, ref Vector512 co, in HeifColorConversionParameters parameters) { Vector512 temporary = y - cg; Vector512 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 r, Vector128 g, Vector128 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector128 y, out Vector128 cg, out Vector128 co) @@ -110,7 +110,7 @@ internal abstract partial class Av1ColorConverterBase Vector256 r, Vector256 g, Vector256 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector256 y, out Vector256 cg, out Vector256 co) @@ -127,7 +127,7 @@ internal abstract partial class Av1ColorConverterBase Vector512 r, Vector512 g, Vector512 b, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector512 y, out Vector512 cg, out Vector512 co) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.YCgCoReversibleOperator.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConverter.YCgCoReversibleOperator.cs similarity index 90% rename from src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.YCgCoReversibleOperator.cs rename to src/ImageSharp/Formats/Heif/Color/HeifColorConverter.YCgCoReversibleOperator.cs index c68dfb7deb..2a1efcf391 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.YCgCoReversibleOperator.cs +++ b/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 { /// /// Implements the YCgCo-Re and YCgCo-Ro integer lifting transforms for scalar and SIMD lanes. /// - internal readonly struct Av1YCgCoReversibleColorOperator : IAv1ColorOperator + internal readonly struct HeifYCgCoReversibleColorOperator : IHeifColorOperator { /// public static bool ChromaUsesLumaRange => false; /// [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 y, ref Vector128 cg, ref Vector128 co, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { - Vector128 encodedMaximum = Vector128.Create(parameters.EncodedSampleMaximum); + Vector128 encodedMaximum = Vector128.Create(parameters.LumaSampleMaximum); Vector128 half = Vector128.Create(0.5F); Vector128 yCode = Vector128.ConvertToInt32(Vector128.Floor((y * encodedMaximum) + half)); Vector128 cgCode = Vector128.ConvertToInt32(Vector128.Floor((cg * encodedMaximum) + half)); @@ -71,9 +71,9 @@ internal abstract partial class Av1ColorConverterBase ref Vector256 y, ref Vector256 cg, ref Vector256 co, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { - Vector256 encodedMaximum = Vector256.Create(parameters.EncodedSampleMaximum); + Vector256 encodedMaximum = Vector256.Create(parameters.LumaSampleMaximum); Vector256 half = Vector256.Create(0.5F); Vector256 yCode = Vector256.ConvertToInt32(Vector256.Floor((y * encodedMaximum) + half)); Vector256 cgCode = Vector256.ConvertToInt32(Vector256.Floor((cg * encodedMaximum) + half)); @@ -97,9 +97,9 @@ internal abstract partial class Av1ColorConverterBase ref Vector512 y, ref Vector512 cg, ref Vector512 co, - in Av1ColorConversionParameters parameters) + in HeifColorConversionParameters parameters) { - Vector512 encodedMaximum = Vector512.Create(parameters.EncodedSampleMaximum); + Vector512 encodedMaximum = Vector512.Create(parameters.LumaSampleMaximum); Vector512 half = Vector512.Create(0.5F); Vector512 yCode = Vector512.ConvertToInt32(Vector512.Floor((y * encodedMaximum) + half)); Vector512 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 red, Vector128 green, Vector128 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector128 y, out Vector128 cg, out Vector128 co) @@ -161,7 +161,7 @@ internal abstract partial class Av1ColorConverterBase Vector128 temporary = blueCode + Vector128.ShiftRightArithmetic(coCode, 1); Vector128 cgCode = greenCode - temporary; Vector128 yCode = temporary + Vector128.ShiftRightArithmetic(cgCode, 1); - Vector128 inverseEncodedMaximum = Vector128.Create(1F / parameters.EncodedSampleMaximum); + Vector128 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 red, Vector256 green, Vector256 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector256 y, out Vector256 cg, out Vector256 co) @@ -187,7 +187,7 @@ internal abstract partial class Av1ColorConverterBase Vector256 temporary = blueCode + Vector256.ShiftRightArithmetic(coCode, 1); Vector256 cgCode = greenCode - temporary; Vector256 yCode = temporary + Vector256.ShiftRightArithmetic(cgCode, 1); - Vector256 inverseEncodedMaximum = Vector256.Create(1F / parameters.EncodedSampleMaximum); + Vector256 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 red, Vector512 green, Vector512 blue, - in Av1ColorConversionParameters parameters, + in HeifColorConversionParameters parameters, out Vector512 y, out Vector512 cg, out Vector512 co) @@ -213,7 +213,7 @@ internal abstract partial class Av1ColorConverterBase Vector512 temporary = blueCode + Vector512.ShiftRightArithmetic(coCode, 1); Vector512 cgCode = greenCode - temporary; Vector512 yCode = temporary + Vector512.ShiftRightArithmetic(cgCode, 1); - Vector512 inverseEncodedMaximum = Vector512.Create(1F / parameters.EncodedSampleMaximum); + Vector512 inverseEncodedMaximum = Vector512.Create(1F / parameters.LumaSampleMaximum); y = Vector512.ConvertToSingle(yCode) * inverseEncodedMaximum; cg = Vector512.ConvertToSingle(cgCode) * inverseEncodedMaximum; co = Vector512.ConvertToSingle(coCode) * inverseEncodedMaximum; diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.cs b/src/ImageSharp/Formats/Heif/Color/HeifColorConverter.cs similarity index 68% rename from src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.cs rename to src/ImageSharp/Formats/Heif/Color/HeifColorConverter.cs index aeeccd5084..3735cebb87 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.cs +++ b/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; /// /// Identifies the H.273 matrix operation used between encoded planes and RGB components. /// -internal enum Av1ColorConversionMode +internal enum HeifColorConversionMode { /// /// A coefficient-based YCbCr matrix conversion. @@ -50,16 +50,16 @@ internal enum Av1ColorConversionMode } /// -/// Converts normalized component planes between an AV1 color model and RGB. +/// Converts normalized component planes between an encoded HEIF color model and RGB. /// -internal abstract partial class Av1ColorConverterBase +internal abstract partial class HeifColorConverterBase { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The resolved H.273 conversion parameters. /// Whether the frame contains only luma samples. - 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 /// /// Gets the resolved H.273 conversion parameters. /// - protected Av1ColorConversionParameters Parameters { get; } + protected HeifColorConversionParameters Parameters { get; } /// /// 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; } /// - /// Converts normalized AV1 components to normalized RGB in place. + /// Converts normalized encoded components to normalized RGB in place. /// /// The luma or first color component, replaced by red. /// The first chroma or second color component, replaced by green. @@ -104,7 +104,7 @@ internal abstract partial class Av1ColorConverterBase public abstract void ConvertToRgbInPlace(Span component0, Span component1, Span component2); /// - /// Converts normalized RGB components to normalized AV1 components in place. + /// Converts normalized RGB components to normalized encoded components in place. /// /// The red component, replaced by luma or the first color component. /// The green component, replaced by the first chroma or second color component. @@ -123,16 +123,16 @@ internal abstract partial class Av1ColorConverterBase /// The resolved H.273 conversion parameters. /// Whether the frame contains only luma samples. /// The selected converter. - 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(in parameters, isMonochrome), - Av1ColorConversionMode.YCgCo => new Av1ColorConverter(in parameters, isMonochrome), - Av1ColorConversionMode.Smpte2085 => new Av1ColorConverter(in parameters, isMonochrome), - Av1ColorConversionMode.ConstantLuminance => new Av1ColorConverter(in parameters, isMonochrome), - Av1ColorConversionMode.ICtCp => new Av1ColorConverter(in parameters, isMonochrome), - Av1ColorConversionMode.IptC2 => new Av1ColorConverter(in parameters, isMonochrome), - Av1ColorConversionMode.YCgCoReversible => new Av1ColorConverter(in parameters, isMonochrome), - _ => new Av1ColorConverter(in parameters, isMonochrome), + HeifColorConversionMode.Identity => new HeifColorConverter(in parameters, isMonochrome), + HeifColorConversionMode.YCgCo => new HeifColorConverter(in parameters, isMonochrome), + HeifColorConversionMode.Smpte2085 => new HeifColorConverter(in parameters, isMonochrome), + HeifColorConversionMode.ConstantLuminance => new HeifColorConverter(in parameters, isMonochrome), + HeifColorConversionMode.ICtCp => new HeifColorConverter(in parameters, isMonochrome), + HeifColorConversionMode.IptC2 => new HeifColorConverter(in parameters, isMonochrome), + HeifColorConversionMode.YCgCoReversible => new HeifColorConverter(in parameters, isMonochrome), + _ => new HeifColorConverter(in parameters, isMonochrome), }; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1TransferFunctions.Simd.cs b/src/ImageSharp/Formats/Heif/Color/HeifTransferFunctions.Simd.cs similarity index 93% rename from src/ImageSharp/Formats/Heif/Av1/Av1TransferFunctions.Simd.cs rename to src/ImageSharp/Formats/Heif/Color/HeifTransferFunctions.Simd.cs index 56b867e8ad..de2739126b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1TransferFunctions.Simd.cs +++ b/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; /// -/// 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. /// -internal static partial class Av1TransferFunctions +internal static partial class HeifTransferFunctions { /// /// Converts four nonlinear signal values to their H.273 linear-domain values. @@ -18,7 +18,7 @@ internal static partial class Av1TransferFunctions /// The signaled transfer characteristics. /// The nonlinear signal values. /// The corresponding linear-domain values. - public static Vector128 ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector128 value) + public static Vector128 ToLinear(CicpTransferCharacteristics transferCharacteristics, Vector128 value) => ToLinear, Vector128Operator>(transferCharacteristics, value); /// @@ -27,7 +27,7 @@ internal static partial class Av1TransferFunctions /// The signaled transfer characteristics. /// The nonlinear signal values. /// The corresponding linear-domain values. - public static Vector256 ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector256 value) + public static Vector256 ToLinear(CicpTransferCharacteristics transferCharacteristics, Vector256 value) => ToLinear, Vector256Operator>(transferCharacteristics, value); /// @@ -36,7 +36,7 @@ internal static partial class Av1TransferFunctions /// The signaled transfer characteristics. /// The nonlinear signal values. /// The corresponding linear-domain values. - public static Vector512 ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector512 value) + public static Vector512 ToLinear(CicpTransferCharacteristics transferCharacteristics, Vector512 value) => ToLinear, Vector512Operator>(transferCharacteristics, value); /// @@ -45,7 +45,7 @@ internal static partial class Av1TransferFunctions /// The signaled transfer characteristics. /// The linear signal values. /// The corresponding nonlinear-domain values. - public static Vector128 ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector128 value) + public static Vector128 ToGamma(CicpTransferCharacteristics transferCharacteristics, Vector128 value) => ToGamma, Vector128Operator>(transferCharacteristics, value); /// @@ -54,7 +54,7 @@ internal static partial class Av1TransferFunctions /// The signaled transfer characteristics. /// The linear signal values. /// The corresponding nonlinear-domain values. - public static Vector256 ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector256 value) + public static Vector256 ToGamma(CicpTransferCharacteristics transferCharacteristics, Vector256 value) => ToGamma, Vector256Operator>(transferCharacteristics, value); /// @@ -63,7 +63,7 @@ internal static partial class Av1TransferFunctions /// The signaled transfer characteristics. /// The linear signal values. /// The corresponding nonlinear-domain values. - public static Vector512 ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector512 value) + public static Vector512 ToGamma(CicpTransferCharacteristics transferCharacteristics, Vector512 value) => ToGamma, Vector512Operator>(transferCharacteristics, value); /// @@ -74,7 +74,7 @@ internal static partial class Av1TransferFunctions /// The signaled transfer characteristics. /// The nonlinear signal values. /// The corresponding linear-domain values. - private static TVector ToLinear(ObuTransferCharacteristics transferCharacteristics, TVector value) + private static TVector ToLinear(CicpTransferCharacteristics transferCharacteristics, TVector value) where TVector : struct where TOperator : struct, ITransferVectorOperator { @@ -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(value); - case ObuTransferCharacteristics.Bt470M: + case CicpTransferCharacteristics.Gamma2_2: return Power(TOperator.Min(TOperator.Max(value, zero), one), 2.2F); - case ObuTransferCharacteristics.Bt470BG: + case CicpTransferCharacteristics.Gamma2_8: return Power(TOperator.Min(TOperator.Max(value, zero), one), 2.8F); - case ObuTransferCharacteristics.Smpte240: + case CicpTransferCharacteristics.SmpteSt240: return ToLinearSmpte240(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(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 TOperator.Divide(Power(TOperator.Max(value, zero), 2.6F), TOperator.Create(Smpte428Scale)); - case ObuTransferCharacteristics.Hlg: + case CicpTransferCharacteristics.AribStdB67: return ToLinearHlg(value); default: return ToLinearBt709(value); @@ -137,7 +137,7 @@ internal static partial class Av1TransferFunctions /// The signaled transfer characteristics. /// The linear signal values. /// The corresponding nonlinear-domain values. - private static TVector ToGamma(ObuTransferCharacteristics transferCharacteristics, TVector value) + private static TVector ToGamma(CicpTransferCharacteristics transferCharacteristics, TVector value) where TVector : struct where TOperator : struct, ITransferVectorOperator { @@ -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(value); - case ObuTransferCharacteristics.Bt470M: + case CicpTransferCharacteristics.Gamma2_2: return Power(TOperator.Min(TOperator.Max(value, zero), one), 1F / 2.2F); - case ObuTransferCharacteristics.Bt470BG: + case CicpTransferCharacteristics.Gamma2_8: return Power(TOperator.Min(TOperator.Max(value, zero), one), 1F / 2.8F); - case ObuTransferCharacteristics.Smpte240: + case CicpTransferCharacteristics.SmpteSt240: return ToGammaSmpte240(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(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 Power(TOperator.Multiply(TOperator.Create(Smpte428Scale), TOperator.Max(value, zero)), 1F / 2.6F); - case ObuTransferCharacteristics.Hlg: + case CicpTransferCharacteristics.AribStdB67: return ToGammaHlg(value); default: return ToGammaBt709(value); @@ -516,7 +516,7 @@ internal static partial class Av1TransferFunctions /// /// Contains the stateless vector-width operators used by the shared H.273 transfer-function formulas. /// -internal static class Av1TransferVectorOperators +internal static class HeifTransferVectorOperators { /// /// Defines the lane-wise operations required by the shared H.273 SIMD formulas. diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1TransferFunctions.cs b/src/ImageSharp/Formats/Heif/Color/HeifTransferFunctions.cs similarity index 85% rename from src/ImageSharp/Formats/Heif/Av1/Av1TransferFunctions.cs rename to src/ImageSharp/Formats/Heif/Color/HeifTransferFunctions.cs index 923ee5ef95..3872273b44 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1TransferFunctions.cs +++ b/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; /// -/// Applies the H.273 transfer characteristics used by AV1 color conversion. +/// Applies the H.273 transfer characteristics used by HEIF color conversion. /// -internal static partial class Av1TransferFunctions +internal static partial class HeifTransferFunctions { /// /// The BT.709 and BT.2020 nonlinear scale factor. @@ -91,40 +91,40 @@ internal static partial class Av1TransferFunctions /// The signaled transfer characteristics. /// The nonlinear signal value. /// The corresponding linear-domain value. - 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 /// The signaled transfer characteristics. /// The linear signal value. /// The corresponding nonlinear-domain value. - 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); diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcChromaSampleLocation.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcChromaSampleLocation.cs new file mode 100644 index 0000000000..bc78539e2d --- /dev/null +++ b/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; + +/// +/// Identifies the location of a 4:2:0 chroma sample relative to its associated two-by-two luma sample region. +/// +internal enum HevcChromaSampleLocation : byte +{ + /// + /// The chroma sample is horizontally co-sited with the left luma column and vertically centered. + /// + Left = 0, + + /// + /// The chroma sample is horizontally and vertically centered. + /// + Center = 1, + + /// + /// The chroma sample is co-sited with the top-left luma sample. + /// + TopLeft = 2, + + /// + /// The chroma sample is horizontally centered and co-sited with the top luma row. + /// + Top = 3, + + /// + /// The chroma sample is horizontally co-sited with the left luma column and vertically co-sited with the bottom luma row. + /// + BottomLeft = 4, + + /// + /// The chroma sample is horizontally centered and vertically co-sited with the bottom luma row. + /// + Bottom = 5, +} diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcPictureBuffer.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcPictureBuffer.cs index 272ee74c92..bd2ebd72c1 100644 --- a/src/ImageSharp/Formats/Heif/Hevc/HevcPictureBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcPictureBuffer.cs @@ -26,13 +26,42 @@ internal sealed class HevcPictureBuffer : IDisposable /// The configuration providing the image memory allocator. /// The coded dimensions, precision, and chroma layout. 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; + } + + /// + /// Initializes a new instance of the class for encoder-owned component planes. + /// + /// The configuration providing the image memory allocator. + /// The coded luma width. + /// The coded luma height. + /// The luma sample precision. + /// The chroma sample precision. + /// The HEVC chroma-format identifier. + /// Whether 4:4:4 components are coded as separate color planes. + 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; diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcVideoUsabilityInformation.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcVideoUsabilityInformation.cs index 02d63277f9..711a8b92af 100644 --- a/src/ImageSharp/Formats/Heif/Hevc/HevcVideoUsabilityInformation.cs +++ b/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 /// /// Gets the top-field chroma sample-location identifier. /// - public byte ChromaSampleLocationTopField { get; } + public HevcChromaSampleLocation ChromaSampleLocationTopField { get; } /// /// Gets the bottom-field chroma sample-location identifier. /// - public byte ChromaSampleLocationBottomField { get; } + public HevcChromaSampleLocation ChromaSampleLocationBottomField { get; } /// /// Gets a value indicating whether a default display window is present. diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcYuvConverter.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcYuvConverter.cs new file mode 100644 index 0000000000..c1d6a233e8 --- /dev/null +++ b/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; + +/// +/// Converts between reconstructed HEVC component planes and packed ImageSharp pixels. +/// +internal static class HevcYuvConverter +{ + /// + /// The largest value represented by an eight-bit packed RGB component. + /// + private const float ByteMaximum = byte.MaxValue; + + /// + /// The largest value represented by a 16-bit packed RGB component. + /// + private const float UShortMaximum = ushort.MaxValue; + + /// + /// Gets the horizontal offset in half-luma-sample units for each HEVC 4:2:0 chroma-location code. + /// + private static ReadOnlySpan ChromaLocationX => [0, 1, 0, 1, 0, 1]; + + /// + /// Gets the vertical offset in half-luma-sample units for each HEVC 4:2:0 chroma-location code. + /// + private static ReadOnlySpan ChromaLocationY => [1, 1, 0, 0, 2, 2]; + + /// + /// Converts reconstructed HEVC component planes to packed pixels. + /// + /// The destination pixel type. + /// The configuration used for allocation and pixel conversion. + /// The reconstructed HEVC picture. + /// The destination image frame. + /// The effective H.273 color description. + /// The progressive-frame 4:2:0 chroma sample location. + public static void ConvertToRgb( + Configuration configuration, + HevcPictureBuffer picture, + ImageFrame image, + CicpProfile colorProfile, + HevcChromaSampleLocation chromaSampleLocation) + where TPixel : unmanaged, IPixel + { + HeifColorConversionParameters parameters = GetConversionParameters(picture, colorProfile, out HeifColorConversionMode mode); + HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, picture.ChromaFormat == 0); + YuvToRgbRowConverter converter = new(configuration, picture, image, colorConverter, chromaSampleLocation); + using IMemoryOwner scratchOwner = configuration.MemoryAllocator.Allocate(converter.BufferLength); + Span scratch = scratchOwner.GetSpan(); + + if (converter.UsesBytePacking) + { + using IMemoryOwner proxyOwner = configuration.MemoryAllocator.Allocate(image.Width + 3); + Span 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.Empty); + } + } + + /// + /// Converts packed pixels to the configured HEVC component planes. + /// + /// The source pixel type. + /// The configuration used for allocation and pixel conversion. + /// The source image frame. + /// The destination HEVC picture. + /// The H.273 color description to encode. + /// The progressive-frame 4:2:0 chroma sample location. + public static void ConvertFromRgb( + Configuration configuration, + ImageFrame image, + HevcPictureBuffer picture, + CicpProfile colorProfile, + HevcChromaSampleLocation chromaSampleLocation) + where TPixel : unmanaged, IPixel + { + HeifColorConversionParameters parameters = GetConversionParameters(picture, colorProfile, out HeifColorConversionMode mode); + HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, picture.ChromaFormat == 0); + RgbToYuvRowConverter converter = new(configuration, picture, image, colorConverter, chromaSampleLocation, in parameters); + using IMemoryOwner componentOwner = configuration.MemoryAllocator.Allocate(converter.ComponentBufferLength); + Span components = componentOwner.GetSpan(); + + if (converter.UsesByteInput) + { + converter.Convert(Span.Empty, components); + return; + } + + using IMemoryOwner packedOwner = configuration.MemoryAllocator.Allocate(image.Width); + converter.Convert(packedOwner.GetSpan()[..image.Width], components); + } + + /// + /// Resolves the shared H.273 conversion parameters for an HEVC picture. + /// + /// The picture defining component precision and sampling. + /// The effective H.273 color description. + /// The resolved color conversion operation. + /// The immutable scalar and SIMD conversion parameters. + 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); + + /// + /// Resolves the two chroma rows and quarter-sample weight surrounding a luma row. + /// + /// The luma row coordinate. + /// The vertical chroma subsampling shift. + /// The chroma offset in half-luma-sample units. + /// The last available chroma row. + /// The lower chroma row. + /// The upper chroma row. + /// The upper-row weight with a denominator of four. + 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; + } + + /// + /// Resolves the luma-relative chroma offsets for the selected plane layout. + /// + /// The HEVC picture layout. + /// The signaled 4:2:0 location. + /// The horizontal offset in half-luma-sample units. + /// The vertical offset in half-luma-sample units. + 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]; + } + + /// + /// Converts one reconstructed HEVC row using pooled planar and packed-pixel storage. + /// + /// The destination pixel type. + private readonly struct YuvToRgbRowConverter + where TPixel : unmanaged, IPixel + { + /// + /// The configuration used for packed-pixel conversion. + /// + private readonly Configuration configuration; + + /// + /// The reconstructed HEVC picture. + /// + private readonly HevcPictureBuffer picture; + + /// + /// The destination image frame. + /// + private readonly ImageFrame image; + + /// + /// The selected H.273 color converter. + /// + private readonly HeifColorConverterBase colorConverter; + + /// + /// The horizontal chroma offset in half-luma-sample units. + /// + private readonly int horizontalPosition; + + /// + /// The vertical chroma offset in half-luma-sample units. + /// + private readonly int verticalPosition; + + /// + /// The horizontal chroma subsampling shift. + /// + private readonly int subX; + + /// + /// The vertical chroma subsampling shift. + /// + private readonly int subY; + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration used for pixel conversion. + /// The reconstructed HEVC picture. + /// The destination image frame. + /// The selected H.273 color converter. + /// The progressive-frame 4:2:0 chroma sample location. + public YuvToRgbRowConverter( + Configuration configuration, + HevcPictureBuffer picture, + ImageFrame 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); + } + + /// + /// Gets a value indicating whether the destination uses JPEG-compatible eight-bit RGB plane packing. + /// + public bool UsesBytePacking => this.picture.BitDepthLuma == 8 && (this.picture.ChromaFormat == 0 || this.picture.BitDepthChroma == 8); + + /// + /// Gets the number of float elements required by the reusable row buffer. + /// + public int BufferLength + { + get + { + int componentRowCount = this.picture.ChromaFormat == 0 ? 3 : 5; + int packedRowCount = this.UsesBytePacking ? 1 : 2; + return this.image.Width * (componentRowCount + packedRowCount); + } + } + + /// + /// Converts one reconstructed row to packed pixels. + /// + /// The zero-based luma row. + /// The reusable pooled row buffer. + /// The padded byte-packing destination when the image row has insufficient padding. + public void Convert(int y, Span scratch, Span proxy) + { + int width = this.image.Width; + Span red = scratch[..width]; + Span green = scratch.Slice(width, width); + Span blue = scratch.Slice(width * 2, width); + ConvertSamplesToFloat(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 cb0 = this.picture.GetRowSpan(HevcPlane.Cb, y0); + ReadOnlySpan cb1 = this.picture.GetRowSpan(HevcPlane.Cb, y1); + ReadOnlySpan cr0 = this.picture.GetRowSpan(HevcPlane.Cr, y0); + ReadOnlySpan cr1 = this.picture.GetRowSpan(HevcPlane.Cr, y1); + Span chroma0 = scratch.Slice(width * 3, width); + Span chroma1 = scratch.Slice(width * 4, width); + bool isCenteredX = this.subX != 0 && this.horizontalPosition == 1; + ReconstructChromaRow(cb0, cb1, y1Weight, this.subX, isCenteredX, green, chroma0, chroma1); + ReconstructChromaRow(cr0, cr1, y1Weight, this.subX, isCenteredX, blue, chroma0, chroma1); + packedOffset = width * 5; + } + + this.colorConverter.ConvertToRgbInPlace(red, green, blue); + Span destination = this.image.PixelBuffer.DangerousGetRowSpan(y); + Span 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 byteStorage = MemoryMarshal.AsBytes(packedStorage)[..(width * 3)]; + Span redBytes = byteStorage[..width]; + Span greenBytes = byteStorage.Slice(width, width); + Span 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 paddedDestination)) + { + PixelOperations.Instance.PackFromRgbPlanes(redBytes, greenBytes, blueBytes, paddedDestination); + } + else + { + PixelOperations.Instance.PackFromRgbPlanes(redBytes, greenBytes, blueBytes, proxy); + proxy[..width].CopyTo(destination); + } + + return; + } + + Span packed = MemoryMarshal.Cast(packedStorage)[..width]; + PackRgba64(red, green, blue, packed); + PixelOperations.Instance.FromRgba64(this.configuration, packed, destination); + } + } + + /// + /// Converts packed image rows to HEVC component planes using pooled planar storage. + /// + /// The source pixel type. + private readonly struct RgbToYuvRowConverter + where TPixel : unmanaged, IPixel + { + /// + /// The configuration used for packed-pixel conversion. + /// + private readonly Configuration configuration; + + /// + /// The destination HEVC picture. + /// + private readonly HevcPictureBuffer picture; + + /// + /// The source image frame. + /// + private readonly ImageFrame image; + + /// + /// The selected H.273 color converter. + /// + private readonly HeifColorConverterBase colorConverter; + + /// + /// The largest encoded luma sample value. + /// + private readonly float lumaMaximum; + + /// + /// The largest encoded chroma sample value. + /// + private readonly float chromaMaximum; + + /// + /// The horizontal chroma offset in half-luma-sample units. + /// + private readonly int horizontalPosition; + + /// + /// The vertical chroma offset in half-luma-sample units. + /// + private readonly int verticalPosition; + + /// + /// The horizontal chroma subsampling shift. + /// + private readonly int subX; + + /// + /// The vertical chroma subsampling shift. + /// + private readonly int subY; + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration used for pixel conversion. + /// The destination HEVC picture. + /// The source image frame. + /// The selected H.273 color converter. + /// The progressive-frame 4:2:0 chroma sample location. + /// The resolved H.273 component ranges. + public RgbToYuvRowConverter( + Configuration configuration, + HevcPictureBuffer picture, + ImageFrame 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); + } + + /// + /// Gets a value indicating whether source pixels use JPEG-compatible eight-bit RGB plane unpacking. + /// + public bool UsesByteInput => this.picture.BitDepthLuma == 8 && (this.picture.ChromaFormat == 0 || this.picture.BitDepthChroma == 8); + + /// + /// Gets the number of float elements required by the reusable component buffer. + /// + public int ComponentBufferLength => this.image.Width * (this.subY == 0 ? 3 : 6); + + /// + /// Converts every packed source row to the destination component planes. + /// + /// The reusable high-bit-depth RGB staging row. + /// The reusable planar component buffer. + public void Convert(Span packed, Span 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 luma = components.Slice(offset, width); + Span chromaBlue = components.Slice(offset + width, width); + Span chromaRed = components.Slice(offset + (width * 2), width); + this.ConvertSourceRow(y, packed, luma, chromaBlue, chromaRed); + WriteSamples( + 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.Empty, Span.Empty, 0F); + continue; + } + + bool isLastRow = y == this.image.Height - 1; + if (this.verticalPosition == 0 && (y & 1) == 0) + { + this.WriteChromaRows(y >> 1, chromaBlue, chromaRed, Span.Empty, Span.Empty, 0F); + } + else if (this.verticalPosition == 1 && ((y & 1) != 0 || isLastRow)) + { + if ((y & 1) != 0) + { + Span previousBlue = components.Slice(width, width); + Span previousRed = components.Slice(width * 2, width); + this.WriteChromaRows(y >> 1, previousBlue, previousRed, chromaBlue, chromaRed, 0.5F); + } + else + { + this.WriteChromaRows(y >> 1, chromaBlue, chromaRed, Span.Empty, Span.Empty, 0F); + } + } + else if (this.verticalPosition == 2 && ((y & 1) != 0 || isLastRow)) + { + this.WriteChromaRows(y >> 1, chromaBlue, chromaRed, Span.Empty, Span.Empty, 0F); + } + } + } + + /// + /// Converts one packed source row into normalized planar components. + /// + /// The source row index. + /// The high-bit-depth RGB staging row. + /// The destination luma or first component values. + /// The destination blue-difference or second component values. + /// The destination red-difference or third component values. + private void ConvertSourceRow(int y, Span packed, Span luma, Span chromaBlue, Span chromaRed) + { + ReadOnlySpan 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.Instance.UnpackIntoRgbPlanes(luma, chromaBlue, chromaRed, source); + this.colorConverter.ConvertFromRgbInPlace(luma, chromaBlue, chromaRed, ByteMaximum); + return; + } + + PixelOperations.Instance.ToRgb48(this.configuration, source, packed); + DeinterleaveRgb48(packed, luma, chromaBlue, chromaRed); + this.colorConverter.ConvertFromRgbInPlace(luma, chromaBlue, chromaRed, UShortMaximum); + } + + /// + /// Filters and writes one pair of chroma component rows. + /// + /// The destination chroma row. + /// The first blue-difference source row. + /// The first red-difference source row. + /// The optional second blue-difference source row. + /// The optional second red-difference source row. + /// The second-row contribution. + private void WriteChromaRows( + int destinationY, + ReadOnlySpan blue0, + ReadOnlySpan red0, + ReadOnlySpan blue1, + ReadOnlySpan red1, + float row1Weight) + { + Span blueDestination = this.picture.GetRowSpan(HevcPlane.Cb, destinationY); + Span redDestination = this.picture.GetRowSpan(HevcPlane.Cr, destinationY); + if (this.subX == 0) + { + WriteSamples( + blue0, + blueDestination, + this.colorConverter.ChromaScale, + this.colorConverter.ChromaBias, + this.chromaMaximum); + + WriteSamples( + red0, + redDestination, + this.colorConverter.ChromaScale, + this.colorConverter.ChromaBias, + this.chromaMaximum); + + return; + } + + bool isCenteredX = this.horizontalPosition == 1; + WriteSubsampledSamples( + blue0, + blue1, + blueDestination, + isCenteredX, + row1Weight, + this.colorConverter.ChromaScale, + this.colorConverter.ChromaBias, + this.chromaMaximum); + + WriteSubsampledSamples( + red0, + red1, + redDestination, + isCenteredX, + row1Weight, + this.colorConverter.ChromaScale, + this.colorConverter.ChromaBias, + this.chromaMaximum); + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Heif/HevcColorConversionBenchmarks.cs b/tests/ImageSharp.Benchmarks/Codecs/Heif/HevcColorConversionBenchmarks.cs new file mode 100644 index 0000000000..11f6ee6580 --- /dev/null +++ b/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; + +/// +/// Measures frame-wide HEVC YUV 4:2:0 color conversion in both directions. +/// +[MemoryDiagnoser(displayGenColumns: false)] +public class HevcColorConversionBenchmarks +{ + /// + /// The benchmark frame width. + /// + private const int Width = 1920; + + /// + /// The benchmark frame height. + /// + private const int Height = 1080; + + /// + /// The eight-bit source RGB image. + /// + private Image byteSource = null!; + + /// + /// The eight-bit destination RGB image. + /// + private Image byteDestination = null!; + + /// + /// The high-bit-depth source RGB image. + /// + private Image highBitDepthSource = null!; + + /// + /// The high-bit-depth destination RGB image. + /// + private Image highBitDepthDestination = null!; + + /// + /// The reusable HEVC component planes. + /// + private HevcPictureBuffer picture = null!; + + /// + /// The H.273 profile used by both conversion directions. + /// + private CicpProfile colorProfile = null!; + + /// + /// Gets or sets the encoded HEVC bit depth. + /// + [Params(8, 10, 12)] + public int BitDepth { get; set; } + + /// + /// Allocates and populates deterministic full-HD RGB and YUV frames outside the measured operations. + /// + [GlobalSetup] + public void Setup() + { + this.byteSource = new Image(Width, Height); + this.byteDestination = new Image(Width, Height); + this.highBitDepthSource = new Image(Width, Height); + this.highBitDepthDestination = new Image(Width, Height); + for (int y = 0; y < Height; y++) + { + Span byteRow = this.byteSource.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y); + Span 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); + } + } + + /// + /// Releases the benchmark images and component planes. + /// + [GlobalCleanup] + public void Cleanup() + { + this.picture.Dispose(); + this.highBitDepthDestination.Dispose(); + this.highBitDepthSource.Dispose(); + this.byteDestination.Dispose(); + this.byteSource.Dispose(); + } + + /// + /// Measures full-frame YUV-to-RGB conversion, including chroma reconstruction and packed-pixel conversion. + /// + /// A converted component that keeps the frame result observable. + [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; + } + + /// + /// Measures full-frame RGB-to-YUV conversion, including planar unpacking and chroma downsampling. + /// + /// An encoded luma sample that keeps the frame result observable. + [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]; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransferFunctionsTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Color/HeifTransferFunctionsTests.cs similarity index 62% rename from tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransferFunctionsTests.cs rename to tests/ImageSharp.Tests/Formats/Heif/Color/HeifTransferFunctionsTests.cs index 8b6b0de317..2e90d5d2d4 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransferFunctionsTests.cs +++ b/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; /// -/// 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. /// -[Trait("Format", "Avif")] -public class Av1TransferFunctionsTests +[Trait("Format", "Heif")] +public class HeifTransferFunctionsTests { private static readonly float[] SignalValues = [ @@ -38,23 +38,23 @@ public class Av1TransferFunctionsTests /// public static TheoryData 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, }; /// @@ -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 vector128 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128.Count))); - Vector256 vector256 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256.Count))); - Vector512 vector512 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector512.Create(SignalValues)); + Vector128 vector128 = HeifTransferFunctions.ToLinear(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128.Count))); + Vector256 vector256 = HeifTransferFunctions.ToLinear(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256.Count))); + Vector512 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 vector128 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128.Count))); - Vector256 vector256 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256.Count))); - Vector512 vector512 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector512.Create(SignalValues)); + Vector128 vector128 = HeifTransferFunctions.ToGamma(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128.Count))); + Vector256 vector256 = HeifTransferFunctions.ToGamma(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256.Count))); + Vector512 vector512 = HeifTransferFunctions.ToGamma(transferCharacteristics, Vector512.Create(SignalValues)); AssertVectorMatchesScalar(expected, vector128, transferCharacteristics); AssertVectorMatchesScalar(expected, vector256, transferCharacteristics); @@ -103,7 +103,7 @@ public class Av1TransferFunctionsTests /// The scalar results. /// The SIMD results. /// The transfer characteristic under test. - private static void AssertVectorMatchesScalar(ReadOnlySpan expected, Vector128 actual, ObuTransferCharacteristics transferCharacteristics) + private static void AssertVectorMatchesScalar(ReadOnlySpan expected, Vector128 actual, CicpTransferCharacteristics transferCharacteristics) { for (int i = 0; i < Vector128.Count; i++) { @@ -117,7 +117,7 @@ public class Av1TransferFunctionsTests /// The scalar results. /// The SIMD results. /// The transfer characteristic under test. - private static void AssertVectorMatchesScalar(ReadOnlySpan expected, Vector256 actual, ObuTransferCharacteristics transferCharacteristics) + private static void AssertVectorMatchesScalar(ReadOnlySpan expected, Vector256 actual, CicpTransferCharacteristics transferCharacteristics) { for (int i = 0; i < Vector256.Count; i++) { @@ -131,7 +131,7 @@ public class Av1TransferFunctionsTests /// The scalar results. /// The SIMD results. /// The transfer characteristic under test. - private static void AssertVectorMatchesScalar(ReadOnlySpan expected, Vector512 actual, ObuTransferCharacteristics transferCharacteristics) + private static void AssertVectorMatchesScalar(ReadOnlySpan expected, Vector512 actual, CicpTransferCharacteristics transferCharacteristics) { for (int i = 0; i < Vector512.Count; i++) { @@ -147,7 +147,7 @@ public class Av1TransferFunctionsTests /// The transfer characteristic under test. /// The SIMD lane index. /// The SIMD register width. - 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( diff --git a/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcYuvConverterTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcYuvConverterTests.cs new file mode 100644 index 0000000000..1b66a49b17 --- /dev/null +++ b/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; + +/// +/// Verifies the shared SIMD-first HEVC component conversion pipeline in both directions. +/// +[Trait("Format", "Heic")] +public class HevcYuvConverterTests +{ + /// + /// Verifies that the identity matrix preserves every eight-bit RGB component through the HEVC GBR plane order. + /// + [Fact] + public void IdentityRoundTripIsExact() + { + const int width = 19; + const int height = 7; + CicpProfile profile = CreateProfile(CicpMatrixCoefficients.Identity, true); + using Image source = new(width, height); + for (int y = 0; y < height; y++) + { + Span 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 destination = new(width, height); + HevcYuvConverter.ConvertToRgb( + Configuration.Default, + picture, + destination.Frames.RootFrame, + profile, + HevcChromaSampleLocation.Left); + + for (int y = 0; y < height; y++) + { + ReadOnlySpan expected = source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y); + ReadOnlySpan actual = destination.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y); + Assert.True(expected.SequenceEqual(actual)); + } + } + + /// + /// Verifies that every exposed precision and plane layout executes both conversion directions on an odd-sized image. + /// + /// The encoded component precision. + /// The HEVC chroma-format identifier. + /// Whether components use their complete numeric range. + [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 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 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); + } + } + + /// + /// Verifies the six HEVC 4:2:0 sample locations in both conversion directions. + /// + /// The signaled sample-location code point. + [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 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 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); + } + + /// + /// Verifies that encoding selects the horizontal and vertical sample coordinates defined by all six location code points. + /// + [Fact] + public void ChromaSampleLocationsSelectExpectedSourceCoordinatesWhenEncoding() + { + CicpProfile profile = CreateProfile(CicpMatrixCoefficients.YCgCo, true); + using Image 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 expectedChromaBlue = [96, 128, 128, 192, 64, 64]; + ReadOnlySpan 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]); + } + } + + /// + /// Verifies that decoding reconstructs chroma at the coordinates defined by all six location code points. + /// + [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 expectedRed = [96, 128, 128, 160, 64, 96]; + ReadOnlySpan 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 topChromaBlue = picture.GetRowSpan(HevcPlane.Cb, 0); + Span bottomChromaBlue = picture.GetRowSpan(HevcPlane.Cb, 1); + topChromaBlue[0] = 128; + topChromaBlue[1] = 255; + bottomChromaBlue[0] = 0; + bottomChromaBlue[1] = 128; + + using Image 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); + } + } + + /// + /// Verifies that independent luma and chroma precisions use their own H.273 code ranges. + /// + [Fact] + public void SupportsIndependentLumaAndChromaBitDepths() + { + CicpProfile profile = CreateProfile(CicpMatrixCoefficients.ItuRBt709_6, false); + using Image 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 destination = new(17, 5); + HevcYuvConverter.ConvertToRgb( + Configuration.Default, + picture, + destination.Frames.RootFrame, + profile, + HevcChromaSampleLocation.Left); + } + + /// + /// Verifies the exact limited-range endpoints written for ten-bit luma and chroma samples. + /// + [Fact] + public void LimitedRangeUsesExactTenBitEndpoints() + { + CicpProfile profile = CreateProfile(CicpMatrixCoefficients.ItuRBt709_6, false); + using Image 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]); + } + + /// + /// Creates a complete H.273 profile for a conversion test. + /// + /// The matrix-coefficient code point. + /// Whether components use their complete numeric range. + /// The configured profile. + private static CicpProfile CreateProfile(CicpMatrixCoefficients matrixCoefficients, bool fullRange) + => new( + (byte)CicpColorPrimaries.ItuRBt709_6, + (byte)CicpTransferCharacteristics.ItuRBt709_6, + (byte)matrixCoefficients, + fullRange); +}