diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md
index a92be5f56d..a34296e648 100644
--- a/HEIF_IMPLEMENTATION_PLAN.md
+++ b/HEIF_IMPLEMENTATION_PLAN.md
@@ -29,7 +29,7 @@ Checkboxes may be marked complete only when the implementation and the verificat
## Delivery dashboard
-Last reconciled with the source tree on 2026-08-29 against production checkpoint `8b9aa4b2cfe59ff0f8c348d9bb06678a5ff12f2a` and the verified equal-average compound work described below. Committed checkpoints include the AV1 transform architecture, OBU framing, intra-block copy, 12-profile reconstruction matrix, layered-item properties, layered reference/header/CDF/motion-field state, inter-frame intra blocks, SIMD-first translational prediction, and complete single-reference inter reconstruction. The current checkpoint adds compound reference trees and modes, paired reference-MV derivation, reference-dependent bounded sequence decoding, and allocation-free SIMD-first equal averaging. A pinned libavif/libaom 19-frame YUV444 sequence passes exact final native-plane and RGBA presentation comparison, production `DecodeBlock()` coverage, constrained allocation, and exactly-once allocator-return tracking. Both source target builds pass with zero warnings and errors; the test-project analyzer build passes with zero errors and only pre-existing repository warnings; 119 direct focused cases and three `FeatureTestRunner` cases pass with zero failures or skips; and `git diff --check` is clean. This dashboard is the authoritative delivery order. The detailed phase checklists below provide subsystem evidence; they do not override the current-stage marker or permit work to skip ahead.
+Last reconciled with the source tree on 2026-08-29 against production checkpoint `e1a764b8f2e8a8db98a003fb3b913213f40084db` and the verified selectable-compound checkpoint awaiting commit. Committed checkpoints include the AV1 transform architecture, OBU framing, intra-block copy, 12-profile reconstruction matrix, layered-item properties, layered reference/header/CDF/motion-field state, inter-frame intra blocks, SIMD-first translational prediction, complete single-reference inter reconstruction, compound reference trees and modes, paired reference-MV derivation, reference-dependent bounded sequence decoding, and allocation-free SIMD-first equal averaging. The verified working tree additionally implements selected inter-intra prediction plus distance-weighted, wedge, and difference-weighted compound blending. Four pinned bounded sequences pass exact native-plane and presentation comparison, normative syntax and production reconstruction pass directly at 8/10/12 bit, SIMD-first mask and blend paths pass through `FeatureTestRunner`, and constrained allocation returns every tracked buffer exactly once. Both source targets build with zero warnings and errors; the `net10.0` test project builds with zero errors and its 1,013 pre-existing warnings; 25 direct focused cases and six `FeatureTestRunner` cases pass without failures or skips; the documentation review is complete; and `git diff --check` is clean. This dashboard is the authoritative delivery order. The detailed phase checklists below provide subsystem evidence; they do not override the current-stage marker or permit work to skip ahead.
Status meanings:
@@ -39,15 +39,15 @@ Status meanings:
- **Not started:** supporting primitives may exist, but the production format path is absent.
- **Current:** the only work item that should be advanced before taking the next queued item.
-Current development stage: **Stage 3 — complete AV1 still-image decoding.** The decoder retains reference/header/CDF/motion-field state, derives frame-level skip-mode references, consumes temporal segment prediction, decodes intra-coded blocks inside inter frames, reconstructs single-reference NEAREST/NEAR/NEW/GLOBAL translational blocks, and now reconstructs two retained predictors with equal-weight compound averaging before residual traversal. Exact dependent-frame and 19-frame compound-sequence native/presentation comparisons, constrained allocation, and ownership evidence pass. Selectable distance-weighted and masked compound blending, inter-intra prediction, OBMC, warped motion, scaled references, and non-translational global prediction remain explicitly unsupported. Neither AV1 nor HEVC production encoding is implemented.
+Current development stage: **Stage 3 — complete AV1 still-image decoding.** The decoder retains reference/header/CDF/motion-field state, derives frame-level skip-mode references, consumes temporal segment prediction, decodes intra-coded blocks inside inter frames, reconstructs single-reference NEAREST/NEAR/NEW/GLOBAL translational blocks, and reconstructs equal-average, distance-weighted, wedge, difference-weighted, and inter-intra prediction before residual traversal. The selectable-compound checkpoint is verified and awaiting commit. OBMC, warped motion, scaled references, and non-translational global prediction remain explicitly unsupported. Neither AV1 nor HEVC production encoding is implemented.
-Immediate checkpoint: **implement and independently verify selected inter-intra prediction and selectable compound blending before advancing to OBMC.** Preserve the committed translational predictor and equal-average rounding contracts, consume each selection tree in normative order, keep mask and predictor workspaces allocator-owned and allocation-free per block, and prove each production branch with independently encoded bounded AVIF content and exact native-plane comparison. This work remains inside the existing image-item and bounded image-sequence surfaces and must not expand into a general ISO BMFF/video model.
+Immediate checkpoint: **commit the verified selected inter-intra and selectable compound checkpoint, then advance to OBMC.** Preserve the committed translational and compound-prediction contracts, keep predictor workspaces allocator-owned and allocation-free per block, and require independently encoded bounded AVIF content with exact native-plane comparison for each next production branch. This work remains inside the existing image-item and bounded image-sequence surfaces and must not expand into a general ISO BMFF/video model.
| Order | Delivery stage | State | Delivered state | Gate that remains open |
| --- | --- | --- | --- | --- |
| 1 | Baseline, provenance, documentation, and public contract | In progress | Pinned codec references, a bounded image-only scope, encoder options, typed bit depth, decoder-option propagation, and extensive HEIF documentation exist. | Complete the all-file documentation audit, record a fresh Release baseline, finish distinct public HEIC/AVIF save boundaries, and close API review. |
| 2 | Bounded HEIF item and image-sequence container | In progress | Still-item parsing, grids, auxiliary alpha, metadata properties, bounded image-sequence tracks, Identify, and all-sync AV1 sequence presentation are connected. | Complete adversarial boundary coverage, remaining item/property behavior, reference-dependent sequence reconstruction, and the bounded sequence writer. |
-| 3 | Still-image AV1 and HEVC decoding | **Current** | HEVC reconstruction reaches exact HM/libheif fixtures across the recorded 8/10/12-bit and chroma cases. AV1 work includes bounded OBU framing, reconstruction, filters, grain, color, transforms, intra-block copy, an exact independent 12-profile bit-depth/chroma matrix through every dispatch tier, retained layered reference/header/CDF/motion-field state, temporal segment prediction, inter-frame intra blocks, SIMD-first translational prediction, exact single-reference inter reconstruction, and exact equal-average compound reconstruction through a reference-dependent bounded sequence. | Implement inter-intra and selectable compound blending, then OBMC, scaled-reference, warped, and non-translational global prediction; remove every other valid AV1 still-image unsupported branch with independent vectors; then complete the remaining HEVC profile and Range Extensions matrix. |
+| 3 | Still-image AV1 and HEVC decoding | **Current** | HEVC reconstruction reaches exact HM/libheif fixtures across the recorded 8/10/12-bit and chroma cases. AV1 work includes bounded OBU framing, reconstruction, filters, grain, color, transforms, intra-block copy, an exact independent 12-profile bit-depth/chroma matrix through every dispatch tier, retained layered reference/header/CDF/motion-field state, temporal segment prediction, inter-frame intra blocks, SIMD-first translational prediction, exact single-reference inter reconstruction, exact equal-average compound reconstruction, and verified selectable compound and inter-intra reconstruction through reference-dependent bounded sequences. | Commit the verified checkpoint, then implement OBMC, scaled-reference, warped, and non-translational global prediction; remove every other valid AV1 still-image unsupported branch with independent vectors; then complete the remaining HEVC profile and Range Extensions matrix. |
| 4 | Complete decoded presentation and animation | In progress | Shared SIMD-first AV1/HEVC color conversion, ICC application, grids, transforms, direct planar alpha composition, frame metadata, repetition, and independently decodable AV1 sequence samples exist. | Close the full color/ICC cross-product, HEVC sequence decoding, AV1/HEVC reference-dependent samples, frame-local metadata/alpha behavior, and independent animated decode vectors. |
| 5 | AV1/AVIF encoding | Not started | RGB-to-planar conversion, forward transforms, OBU writer foundations, options, and container-writing infrastructure exist. | `HeifEncoderCore` still rejects AV1. Implement a real independently decodable lossy/lossless AV1 payload and the complete AVIF item/metadata matrix. |
| 6 | HEVC/HEIC encoding | Not started | Shared input color conversion, options, and HEIF writer infrastructure exist. | `HeifEncoderCore` still rejects HEVC. Implement a real independently decodable lossy/lossless HEVC payload and the complete HEIC item/metadata matrix. |
@@ -89,7 +89,7 @@ Immediate checkpoint: **implement and independently verify selected inter-intra
- [x] Correct the progressive dependent-frame extraction and compare the final frame's first three native planes with pinned libaom output and its final RGBA presentation with pinned libavif exactly. The unmodified AVIF has the recorded SHA-256 and stores the primary color item's 55-byte base extent at offset 511 and 17-byte dependent extent at offset 583. The corrected logical `.bit` payload decodes as two YUV444 frames with pinned libaom `03087864cf4bea6abb0d28f95cf7843511413d8f`; the retained two-frame YUV444-alpha reference and final PNG come from pinned libavif linked to that build. The production test selects the second native frame, requires inter-coded blocks, and passes exact native and presentation comparisons through `FeatureTestRunner`.
- [x] Decode all compound reference-pair trees and inter modes, retain paired spatial/temporal reference-MV candidates through DRL selection, and reconstruct two translational predictors with allocation-free equal-weight averaging across 8/10/12-bit samples without changing the single-reference predictor contract or rounding model.
- [x] Verify the equal-average production branch with the pinned 19-frame libavif YUV444 sequence. The final native Y, U, and V planes match pinned libaom exactly, final RGBA presentation matches pinned libavif exactly, production `DecodeBlock()` is covered at 8/10/12 bit, constrained plane allocation passes, and allocator tracking returns every retained and temporal motion-field buffer exactly once.
- - [ ] Decode selected inter-intra prediction and selectable distance-weighted, wedge, and difference-weighted compound blending.
+ - [x] Decode selected inter-intra prediction and selectable distance-weighted, wedge, and difference-weighted compound blending. Normative-order syntax, direct 8/10/12-bit production reconstruction, SIMD/scalar arithmetic, four pinned bounded sequences, exact final native planes and presentation, constrained contiguous planes, and balanced allocator returns pass. Both source targets build with zero warnings and errors; the test project builds with zero errors and pre-existing repository warnings; 25 direct focused cases and six `FeatureTestRunner` cases pass without failures or skips; and `git diff --check` is clean.
- [ ] Decode OBMC, scaled references, warped motion, and non-translational global-motion prediction.
- [ ] Verify every remaining connected mode and filter with independently encoded bounded AV1 content and exact native-plane comparisons.
- [ ] Return the explicitly selected spatial layer or the final displayed layer, keeping reference reconstruction separate from display-only film grain.
@@ -352,7 +352,7 @@ This table is intentionally incomplete. Add a row before each additional AV1 or
## Current implementation assessment
-This assessment was reconciled with the source tree on 2026-08-29, including production checkpoint `8b9aa4b2cfe59ff0f8c348d9bb06678a5ff12f2a` and the verified equal-average compound checkpoint. Unless a result is stated explicitly, each item is a source-inspection finding rather than a verified interoperability claim.
+This assessment was reconciled with the source tree on 2026-08-29, including production checkpoint `e1a764b8f2e8a8db98a003fb3b913213f40084db`. Unless a result is stated explicitly, each item is a source-inspection finding rather than a verified interoperability claim.
### Public integration
@@ -391,7 +391,7 @@ This assessment was reconciled with the source tree on 2026-08-29, including pro
- The bounded `Av1Decoder` session parses all tile state before allocating and reconstructing each coded image layer or sequence sample. After successful completion it retains the ungrained reference planes, frame header, frame information, and published entropy snapshot in the refreshed slots, while presentation-only ownership remains separate. A new accepted sequence header resets both parser and retained-owner state. Bounded image-sequence decoding accepts `show_existing_frame`, including its key-frame reference reset and separate film-grain presentation ownership, without exposing a general animation/video reference model.
- Committed inter-frame support parses single-reference selection, builds the fixed-capacity spatial and temporal reference-MV stack, decodes NEAREST/NEAR/NEW/GLOBAL and DRL syntax, decodes or infers interpolation filters, checks inter-intra and motion-mode eligibility, and invokes simple translational prediction before residual reconstruction. Commit `8b9aa4b2cfe59ff0f8c348d9bb06678a5ff12f2a` records exact corrected dependent-frame native-plane and final-presentation comparisons plus the complete focused Release matrix and ownership/allocation evidence.
-- The verified equal-average compound checkpoint owns every compound reference-selection and inter-mode distribution through the retained CDF lifecycle, derives primary and secondary candidate vectors as inseparable pairs, reconstructs both retained references through the existing translational predictor, and combines them through one SIMD-first byte/ushort averaging operator with scalar parity. The pinned 19-frame libavif sequence exercises the complete production block branch and matches pinned libaom native planes and pinned libavif presentation exactly after decoding all preceding reference samples.
+- Commit `e1a764b8f2e8a8db98a003fb3b913213f40084db` owns every compound reference-selection and inter-mode distribution through the retained CDF lifecycle, derives primary and secondary candidate vectors as inseparable pairs, reconstructs both retained references through the existing translational predictor, and combines them through one SIMD-first byte/ushort averaging operator with scalar parity. The pinned 19-frame libavif sequence exercises the complete production block branch and matches pinned libaom native planes and pinned libavif presentation exactly after decoding all preceding reference samples.
- Source inspection against pinned libaom `03087864cf4bea6abb0d28f95cf7843511413d8f` resolves two disputed audit claims in favor of the current predicates. The interpolation call path uses `is_nontrans_global_motion()`, which returns false only for `TRANSLATION`, so an identity `GLOBALMV` block omits filter symbols. The single-reference spatial extension loops use `MAX_MV_REF_CANDIDATES`, which is two, while the full reference-MV stack capacity is eight. These loops are spatial extension, not temporal extension.
- Every padded AV1 frame plane requests ImageSharp's contiguous allocation contract, and constrained-allocator reconstruction passes without copying. Motion fields use allocator-owned storage and counted leases; allocation tracking passes for initialization, retained aliases, failure unwinding, presentation ownership, decoder-result ownership, and exactly-once disposal. The corrected logical progressive color payload reaches the production inter branch and its final native planes match pinned libaom exactly.
- Transform coefficient entropy derivation and updates now address the above contexts relative to the tile column and the left contexts relative to the current superblock row, preserve luma coordinates independently of chroma subsampling, and test every packed context entry for the libaom any-nonzero rule. Extended vertical partition updates advance the mode-information column rather than the row. The existing multi-superblock 4:4:4 AVIF fixture now completes tile parsing; independent coefficient-context vectors across tile boundaries, chroma layouts, bit depths, and edge-clipped transforms remain required.
@@ -422,7 +422,7 @@ This assessment was reconciled with the source tree on 2026-08-29, including pro
### Tests
- HEVC coverage includes exact native-plane comparison with HM output, exact complete-image comparison with pinned libheif/libde265 references, and the 10 official Sony GENERAL Range Extensions first-picture fixtures across 8/10/12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 reconstruction. The remaining exposed profiles and individual Range Extensions tools still need exact independent vectors.
-- AV1 has focused bitstream, prediction, entropy, reconstruction, filter, film-grain, color, and transform coverage, plus real libavif inputs. A real two-layer libavif-derived OBU stream verifies the bounded frame lifecycle, retained-slot occupancy, resolved inter references, and `frame_size_with_refs` dimensions through a fake tile lifecycle. The corrected progressive color item then verifies its production single-reference inter branch against exact pinned-libaom native planes and pinned-libavif presentation. The pinned 19-frame YUV444 sequence separately reaches production equal-average compound reconstruction after every preceding reference sample and matches its final native and presented references exactly. A real palette stream truncated inside its tile entropy payload verifies libaom-equivalent overflow/trailing-bit rejection and decoder-session recovery. The current compound checkpoint passes 119 direct focused cases plus three `FeatureTestRunner` cases, zero-warning `net10.0` and `net11.0` source builds, a zero-error test-project analyzer build with pre-existing repository warnings, allocator lifetime validation, and `git diff --check`. Valid still-image syntax paths still contain explicit unsupported branches, so the independent AV1 decode matrix is not complete.
+- AV1 has focused bitstream, prediction, entropy, reconstruction, filter, film-grain, color, and transform coverage, plus real libavif inputs. A real two-layer libavif-derived OBU stream verifies the bounded frame lifecycle, retained-slot occupancy, resolved inter references, and `frame_size_with_refs` dimensions through a fake tile lifecycle. The corrected progressive color item then verifies its production single-reference inter branch against exact pinned-libaom native planes and pinned-libavif presentation. The pinned 19-frame YUV444 sequence separately reaches production equal-average compound reconstruction after every preceding reference sample and matches its final native and presented references exactly. A real palette stream truncated inside its tile entropy payload verifies libaom-equivalent overflow/trailing-bit rejection and decoder-session recovery. The committed equal-average checkpoint passes 119 direct focused cases plus three `FeatureTestRunner` cases, zero-warning `net10.0` and `net11.0` source builds, a zero-error test-project analyzer build with pre-existing repository warnings, allocator lifetime validation, and `git diff --check`. Four additional pinned sequences reach distance-weighted, both wedge signs, both difference-mask orientations, and smooth and wedge inter-intra reconstruction with exact final native and presented output. The selectable-compound checkpoint passes both source targets, the test-project analyzer build, 25 direct focused cases, six `FeatureTestRunner` cases, constrained allocation, exactly-once tracked returns, and `git diff --check`. Valid still-image syntax paths still contain explicit unsupported branches, so the independent AV1 decode matrix is not complete.
- The AV1 transform matrix verifies scalar, `Vector128`, `Vector256`, and `Vector512` forward representations plus the production inverse tiers across every valid size/type combination and supported bit depth. All 511 focused forward and inverse cases pass in Release; `FeatureTestRunner` isolates hardware tiers, every two-dimensional configuration exercises production dispatch, and the complete-block benchmark records zero managed allocation.
- Independent libavif fixtures cover primary, grid, auxiliary-alpha, ICC, metadata-skipping, and all-sync image-sequence presentation. Reference-dependent AV1 and HEVC sequence reconstruction and independent HEVC ICC sequence coverage remain open.
- Focused decoder-option tests cover strict, ancillary-only, image-data, and metadata-skipping behavior for still items and sequence samples. Complete adversarial dimension, allocation, malformed-container, and resource-limit coverage remains open.
@@ -569,7 +569,7 @@ Implement and verify in dependency order:
- [x] Correct the logical progressive color payload and compare the final dependent frame's native planes and libavif presentation exactly. Pinned libaom decodes both layers from the primary item's two `iloc` extents, and pinned libavif produces the retained two-frame YUV444-alpha reference and final PNG. The exact production-path comparisons pass through `FeatureTestRunner`.
- [x] Decode all compound reference trees and inter modes, derive and retain paired reference-MV candidates, reconstruct both unscaled translational references, and combine them with equal-weight averaging through `Vector512`, `Vector256`, `Vector128`, and scalar paths without per-block allocation.
- [x] Verify equal-average compound reconstruction with the pinned 19-frame libavif sequence, exact pinned-libaom final native planes, exact pinned-libavif final presentation, direct 8/10/12-bit production-block tests, constrained allocation, and exactly-once motion-field disposal. Both Release source targets pass with zero warnings and errors; the test-project analyzer build passes with zero errors and pre-existing repository warnings; 119 direct focused cases and three `FeatureTestRunner` cases pass without failures or skips; and `git diff --check` is clean.
- - [ ] Decode and reconstruct selected inter-intra prediction and selectable distance-weighted, wedge, and difference-weighted compound blending through reconstructed reference planes.
+ - [x] Decode and reconstruct selected inter-intra prediction and selectable distance-weighted, wedge, and difference-weighted compound blending through reconstructed reference planes. Direct syntax and 8/10/12-bit production tests, SIMD/scalar mask and blend tests, exact four-sequence native/presentation comparisons, constrained contiguous allocation, and exactly-once tracked returns pass. Both source targets build with zero warnings and errors; the test project builds with zero errors and pre-existing repository warnings; 25 direct focused cases and six `FeatureTestRunner` cases pass without failures or skips; and `git diff --check` is clean.
- [ ] Decode and reconstruct OBMC, scaled references, warped motion, and non-translational global motion through reconstructed reference planes.
- [ ] Verify every remaining connected inter mode and filter with independently encoded bounded AV1 content and exact native-plane comparisons.
- [ ] Lossless and high-bit-depth reconstruction with correct clipping and intermediate precision.
@@ -808,8 +808,9 @@ The dashboard and immediate execution queue define the remaining critical path.
- [x] Finish the Phase 8 checkpoint for the implemented AV1 `Vector512` transforms, including Release, feature-isolation, and benchmark evidence.
- [x] Finish the complete focused Release matrix, final source/documentation review, and checkpoint implementation for the simple single-reference inter slice. Commit `8b9aa4b2cfe59ff0f8c348d9bb06678a5ff12f2a` records the corrected real dependent-frame AVIF, exact pinned-libaom native-plane and pinned-libavif presentation comparisons, and 293 focused cases passing with zero failures or skips.
-- [x] Implement compound reference selection and equal averaging with paired reference-MV derivation, allocation-free SIMD/scalar parity, and exact independent native/presentation evidence through a reference-dependent bounded AVIF sequence.
-- [ ] **Current:** implement selected inter-intra prediction and selectable distance-weighted, wedge, and difference-weighted compound blending, then continue the remaining Phase 3 inter modes in dependency order: OBMC, scaled references, warped motion, and non-translational global prediction. Each mode requires independent exact evidence before the next begins.
+- [x] Implement compound reference selection and equal averaging with paired reference-MV derivation, allocation-free SIMD/scalar parity, and exact independent native/presentation evidence through a reference-dependent bounded AVIF sequence. Commit `e1a764b8f2e8a8db98a003fb3b913213f40084db` records the checkpoint.
+- [x] Implement and independently verify selected inter-intra prediction and selectable distance-weighted, wedge, and difference-weighted compound blending through exact pinned native-plane and presentation comparisons, direct 8/10/12-bit production coverage, constrained allocation, and SIMD/scalar parity.
+- [ ] **Next after checkpoint commit:** implement OBMC, then continue the remaining Phase 3 inter modes in dependency order: scaled references, warped motion, and non-translational global prediction. Each mode requires independent exact evidence before the next begins.
- [ ] Remove every other unsupported valid AV1 still-image syntax path, correct the audited 12-bit inverse-transform arithmetic, and prove the complete AVIF decode matrix with independent inputs and scalar/SIMD parity.
- [ ] Close Phase 4 by completing the remaining HEVC profile and Range Extensions matrix with exact native-plane and presented-image evidence.
- [ ] Close Phase 5 and the decode portion of the bounded sequence ledger: color, ICC, alpha, grids, presentation transforms, reference-dependent samples, and complete animated AVIF/HEIC decode.
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs
index 4e594a7793..277d77449d 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs
@@ -114,6 +114,80 @@ internal static class Av1DefaultDistributions
///
public static Av1Distribution[] InterIntra => [new(16384), new(26887), new(27597), new(30237)];
+ ///
+ /// Gets the four-symbol inter-intra mode distributions indexed by block-size group.
+ ///
+ public static Av1Distribution[] InterIntraMode =>
+ [
+ new(8192, 16384, 24576),
+ new(1875, 11082, 27332),
+ new(2473, 9996, 26388),
+ new(4238, 11537, 25926),
+ ];
+
+ ///
+ /// Gets the inter-intra wedge-selection distributions indexed by block size.
+ ///
+ public static Av1Distribution[] WedgeInterIntra =>
+ [
+ new(16384), new(16384), new(16384), new(20036), new(24957), new(26704),
+ new(27530), new(29564), new(29444), new(26872), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384),
+ ];
+
+ ///
+ /// Gets the wedge-or-difference masked-compound distributions indexed by block size.
+ ///
+ public static Av1Distribution[] CompoundType =>
+ [
+ new(16384), new(16384), new(16384), new(23431), new(13171), new(11470),
+ new(9770), new(9100), new(8233), new(6172), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(11820), new(7701), new(16384), new(16384),
+ ];
+
+ ///
+ /// Gets the sixteen-symbol wedge-index distributions indexed by block size.
+ ///
+ public static Av1Distribution[] WedgeIndex =>
+ [
+ CreateUniformWedgeIndexDistribution(),
+ CreateUniformWedgeIndexDistribution(),
+ CreateUniformWedgeIndexDistribution(),
+ new(2438, 4440, 6599, 8663, 11005, 12874, 15751, 18094, 20359, 22362, 24127, 25702, 27752, 29450, 31171),
+ new(806, 3266, 6005, 6738, 7218, 7367, 7771, 14588, 16323, 17367, 18452, 19422, 22839, 26127, 29629),
+ new(2779, 3738, 4683, 7213, 7775, 8017, 8655, 14357, 17939, 21332, 24520, 27470, 29456, 30529, 31656),
+ new(1684, 3625, 5675, 7108, 9302, 11274, 14429, 17144, 19163, 20961, 22884, 24471, 26719, 28714, 30877),
+ new(1142, 3491, 6277, 7314, 8089, 8355, 9023, 13624, 15369, 16730, 18114, 19313, 22521, 26012, 29550),
+ new(2742, 4195, 5727, 8035, 8980, 9336, 10146, 14124, 17270, 20533, 23434, 25972, 27944, 29570, 31416),
+ new(1727, 3948, 6101, 7796, 9841, 12344, 15766, 18944, 20638, 22038, 23963, 25311, 26988, 28766, 31012),
+ CreateUniformWedgeIndexDistribution(),
+ CreateUniformWedgeIndexDistribution(),
+ CreateUniformWedgeIndexDistribution(),
+ CreateUniformWedgeIndexDistribution(),
+ CreateUniformWedgeIndexDistribution(),
+ CreateUniformWedgeIndexDistribution(),
+ CreateUniformWedgeIndexDistribution(),
+ CreateUniformWedgeIndexDistribution(),
+ new(154, 987, 1925, 2051, 2088, 2111, 2151, 23033, 23703, 24284, 24985, 25684, 27259, 28883, 30911),
+ new(1135, 1322, 1493, 2635, 2696, 2737, 2770, 21016, 22935, 25057, 27251, 29173, 30089, 30960, 31933),
+ CreateUniformWedgeIndexDistribution(),
+ CreateUniformWedgeIndexDistribution(),
+ ];
+
+ ///
+ /// Gets the average-or-distance-weighted compound distributions indexed by derived context.
+ ///
+ public static Av1Distribution[] CompoundIndex =>
+ [new(18244), new(12865), new(7053), new(13259), new(9334), new(4644)];
+
+ ///
+ /// Gets the unmasked-or-masked compound-group distributions indexed by derived context.
+ ///
+ public static Av1Distribution[] CompoundGroupIndex =>
+ [new(26607), new(22891), new(18840), new(24594), new(19934), new(22674)];
+
///
/// Gets the Simple Translation, OBMC, or Warped motion-mode distributions indexed by block size.
///
@@ -2682,6 +2756,12 @@ internal static class Av1DefaultDistributions
public static Av1Distribution[][][] GetEndOfBlockExtra(int baseQIndex)
=> Av1Distribution.CreateCopy(EndOfBlockExtra[GetQContext(baseQIndex)]);
+ ///
+ /// Creates the uniform sixteen-symbol distribution used by block sizes that cannot signal a wedge index.
+ ///
+ private static Av1Distribution CreateUniformWedgeIndexDistribution()
+ => new(2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624, 28672, 30720);
+
///
/// Maps a base quantizer index to one of the four AV1 coefficient-probability initialization bands.
///
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1FrameEntropyContext.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1FrameEntropyContext.cs
index ae3e910b27..a8c3189607 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1FrameEntropyContext.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1FrameEntropyContext.cs
@@ -95,6 +95,12 @@ internal sealed class Av1FrameEntropyContext
this.CompoundBackwardReference = Av1DefaultDistributions.CompoundBackwardReference;
this.InterCompoundMode = Av1DefaultDistributions.InterCompoundMode;
this.InterIntra = Av1DefaultDistributions.InterIntra;
+ this.InterIntraMode = Av1DefaultDistributions.InterIntraMode;
+ this.WedgeInterIntra = Av1DefaultDistributions.WedgeInterIntra;
+ this.CompoundType = Av1DefaultDistributions.CompoundType;
+ this.WedgeIndex = Av1DefaultDistributions.WedgeIndex;
+ this.CompoundIndex = Av1DefaultDistributions.CompoundIndex;
+ this.CompoundGroupIndex = Av1DefaultDistributions.CompoundGroupIndex;
this.MotionMode = Av1DefaultDistributions.MotionMode;
this.Obmc = Av1DefaultDistributions.Obmc;
this.SwitchableInterpolation = Av1DefaultDistributions.SwitchableInterpolation;
@@ -164,6 +170,12 @@ internal sealed class Av1FrameEntropyContext
this.CompoundBackwardReference = Av1Distribution.CreateCopy(source.CompoundBackwardReference);
this.InterCompoundMode = Av1Distribution.CreateCopy(source.InterCompoundMode);
this.InterIntra = Av1Distribution.CreateCopy(source.InterIntra);
+ this.InterIntraMode = Av1Distribution.CreateCopy(source.InterIntraMode);
+ this.WedgeInterIntra = Av1Distribution.CreateCopy(source.WedgeInterIntra);
+ this.CompoundType = Av1Distribution.CreateCopy(source.CompoundType);
+ this.WedgeIndex = Av1Distribution.CreateCopy(source.WedgeIndex);
+ this.CompoundIndex = Av1Distribution.CreateCopy(source.CompoundIndex);
+ this.CompoundGroupIndex = Av1Distribution.CreateCopy(source.CompoundGroupIndex);
this.MotionMode = Av1Distribution.CreateCopy(source.MotionMode);
this.Obmc = Av1Distribution.CreateCopy(source.Obmc);
this.SwitchableInterpolation = Av1Distribution.CreateCopy(source.SwitchableInterpolation);
@@ -332,6 +344,36 @@ internal sealed class Av1FrameEntropyContext
///
public Av1Distribution[] InterIntra { get; }
+ ///
+ /// Gets the inter-intra mode distributions indexed by block-size group.
+ ///
+ public Av1Distribution[] InterIntraMode { get; }
+
+ ///
+ /// Gets the inter-intra wedge-selection distributions indexed by block size.
+ ///
+ public Av1Distribution[] WedgeInterIntra { get; }
+
+ ///
+ /// Gets the masked-compound type distributions indexed by block size.
+ ///
+ public Av1Distribution[] CompoundType { get; }
+
+ ///
+ /// Gets the wedge-index distributions indexed by block size.
+ ///
+ public Av1Distribution[] WedgeIndex { get; }
+
+ ///
+ /// Gets the average-or-distance-weighted compound distributions indexed by derived context.
+ ///
+ public Av1Distribution[] CompoundIndex { get; }
+
+ ///
+ /// Gets the unmasked-or-masked compound-group distributions indexed by derived context.
+ ///
+ public Av1Distribution[] CompoundGroupIndex { get; }
+
///
/// Gets the three-way motion-mode distributions indexed by block size.
///
@@ -523,6 +565,12 @@ internal sealed class Av1FrameEntropyContext
CopyState(source.CompoundBackwardReference, this.CompoundBackwardReference);
CopyState(source.InterCompoundMode, this.InterCompoundMode);
CopyState(source.InterIntra, this.InterIntra);
+ CopyState(source.InterIntraMode, this.InterIntraMode);
+ CopyState(source.WedgeInterIntra, this.WedgeInterIntra);
+ CopyState(source.CompoundType, this.CompoundType);
+ CopyState(source.WedgeIndex, this.WedgeIndex);
+ CopyState(source.CompoundIndex, this.CompoundIndex);
+ CopyState(source.CompoundGroupIndex, this.CompoundGroupIndex);
CopyState(source.MotionMode, this.MotionMode);
CopyState(source.Obmc, this.Obmc);
CopyState(source.SwitchableInterpolation, this.SwitchableInterpolation);
@@ -598,6 +646,12 @@ internal sealed class Av1FrameEntropyContext
ResetUpdateCounts(this.CompoundBackwardReference);
ResetUpdateCounts(this.InterCompoundMode);
ResetUpdateCounts(this.InterIntra);
+ ResetUpdateCounts(this.InterIntraMode);
+ ResetUpdateCounts(this.WedgeInterIntra);
+ ResetUpdateCounts(this.CompoundType);
+ ResetUpdateCounts(this.WedgeIndex);
+ ResetUpdateCounts(this.CompoundIndex);
+ ResetUpdateCounts(this.CompoundGroupIndex);
ResetUpdateCounts(this.MotionMode);
ResetUpdateCounts(this.Obmc);
ResetUpdateCounts(this.SwitchableInterpolation);
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
index 53aea3191c..3dd862b35c 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
@@ -188,6 +189,77 @@ internal static class Av1SymbolContextHelper
3, 5, 5, 5, 6
];
+ ///
+ /// Gets the context that selects unmasked or masked compound prediction.
+ ///
+ /// The above block, or at a tile boundary.
+ /// The left block, or at a tile boundary.
+ /// The context in the inclusive range zero through five.
+ public static int GetCompoundGroupIndexContext(Av1BlockModeInfo? above, Av1BlockModeInfo? left)
+ {
+ int aboveContext = 0;
+ if (above is not null)
+ {
+ aboveContext = above.ReferenceFrames[1] > Av1ReferenceFrameType.Intra
+ ? above.CompoundGroupIndex ? 1 : 0
+ : above.ReferenceFrames[0] == Av1ReferenceFrameType.Alternate ? 3 : 0;
+ }
+
+ int leftContext = 0;
+ if (left is not null)
+ {
+ leftContext = left.ReferenceFrames[1] > Av1ReferenceFrameType.Intra
+ ? left.CompoundGroupIndex ? 1 : 0
+ : left.ReferenceFrames[0] == Av1ReferenceFrameType.Alternate ? 3 : 0;
+ }
+
+ return Math.Min(5, aboveContext + leftContext);
+ }
+
+ ///
+ /// Gets the context that selects equal or distance-weighted compound prediction.
+ ///
+ /// The active order-hint modulo domain.
+ /// The current frame and retained reference order hints.
+ /// The compound block whose references are selected.
+ /// The above block, or at a tile boundary.
+ /// The left block, or at a tile boundary.
+ /// The context in the inclusive range zero through five.
+ public static int GetCompoundIndexContext(
+ ObuOrderHintInfo orderHintInfo,
+ ObuFrameHeader frameHeader,
+ Av1BlockModeInfo modeInfo,
+ Av1BlockModeInfo? above,
+ Av1BlockModeInfo? left)
+ {
+ ReadOnlySpan referenceFrameIndices = frameHeader.GetReferenceFrameIndices();
+ ReadOnlySpan referenceOrderHints = frameHeader.GetReferenceOrderHints();
+ int primaryCanonicalIndex = (int)modeInfo.ReferenceFrames[0] - (int)Av1ReferenceFrameType.Last;
+ int secondaryCanonicalIndex = (int)modeInfo.ReferenceFrames[1] - (int)Av1ReferenceFrameType.Last;
+ uint primaryOrderHint = referenceOrderHints[(int)referenceFrameIndices[primaryCanonicalIndex]];
+ uint secondaryOrderHint = referenceOrderHints[(int)referenceFrameIndices[secondaryCanonicalIndex]];
+ int forwardDistance = Math.Abs(orderHintInfo.GetRelativeDistance(secondaryOrderHint, frameHeader.OrderHint));
+ int backwardDistance = Math.Abs(orderHintInfo.GetRelativeDistance(frameHeader.OrderHint, primaryOrderHint));
+
+ int aboveContext = 0;
+ if (above is not null)
+ {
+ aboveContext = above.ReferenceFrames[1] > Av1ReferenceFrameType.Intra
+ ? above.CompoundIndex ? 1 : 0
+ : above.ReferenceFrames[0] == Av1ReferenceFrameType.Alternate ? 1 : 0;
+ }
+
+ int leftContext = 0;
+ if (left is not null)
+ {
+ leftContext = left.ReferenceFrames[1] > Av1ReferenceFrameType.Intra
+ ? left.CompoundIndex ? 1 : 0
+ : left.ReferenceFrames[0] == Av1ReferenceFrameType.Alternate ? 1 : 0;
+ }
+
+ return aboveContext + leftContext + (forwardDistance == backwardDistance ? 3 : 0);
+ }
+
///
/// Reduces a rectangular transform size to the square context used by transform-size distributions.
///
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
index 7f9f94f961..f35d43e1cc 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
@@ -385,6 +385,73 @@ internal ref struct Av1SymbolDecoder
return r.ReadSymbol(this.context.InterIntra[sizeGroup]) != 0;
}
+ ///
+ /// Reads the intra mode blended into a selected inter-intra block.
+ ///
+ /// The decoded block size that selects the mode distribution.
+ /// The selected inter-intra mode.
+ public Av1InterIntraMode ReadInterIntraMode(Av1BlockSize blockSize)
+ {
+ int sizeGroup = blockSize.GetSizeGroup();
+ ref Av1SymbolReader r = ref this.reader;
+ return (Av1InterIntraMode)r.ReadSymbol(this.context.InterIntraMode[sizeGroup]);
+ }
+
+ ///
+ /// Reads whether a selected inter-intra block uses a wedge mask.
+ ///
+ /// The decoded block size that selects the wedge flag distribution.
+ /// when wedge blending is selected.
+ public bool ReadUseInterIntraWedge(Av1BlockSize blockSize)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.context.WedgeInterIntra[(int)blockSize]) != 0;
+ }
+
+ ///
+ /// Reads a wedge-mask index.
+ ///
+ /// The decoded block size that selects the wedge-index distribution.
+ /// The wedge index in the inclusive range zero through fifteen.
+ public byte ReadWedgeIndex(Av1BlockSize blockSize)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return (byte)r.ReadSymbol(this.context.WedgeIndex[(int)blockSize]);
+ }
+
+ ///
+ /// Reads whether a compound block uses the masked-compound mode group.
+ ///
+ /// The derived neighboring compound-group context.
+ /// for masked compound prediction.
+ public bool ReadCompoundGroupIndex(int context)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.context.CompoundGroupIndex[context]) != 0;
+ }
+
+ ///
+ /// Reads whether an unmasked compound block uses equal averaging.
+ ///
+ /// The derived neighboring compound-index context.
+ /// for equal averaging; otherwise, distance-weighted blending.
+ public bool ReadCompoundIndex(int context)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.context.CompoundIndex[context]) != 0;
+ }
+
+ ///
+ /// Reads the selected masked-compound type for a wedge-capable block.
+ ///
+ /// The decoded block size that selects the masked-compound distribution.
+ /// The selected wedge or difference-weighted compound type.
+ public Av1CompoundType ReadMaskedCompoundType(Av1BlockSize blockSize)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return (Av1CompoundType)((int)Av1CompoundType.Wedge + r.ReadSymbol(this.context.CompoundType[(int)blockSize]));
+ }
+
///
/// Reads the motion model selected for an eligible single-reference inter block.
///
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
index fea22f27df..4ded4d591e 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
@@ -106,6 +106,98 @@ internal class Av1PredictionDecoder
blockModeInfoColumnOffset,
blockModeInfoRowOffset);
+ ///
+ /// Builds the intra predictor for an 8-bit inter-intra plane block in separate caller-owned storage.
+ ///
+ public void DecodeInterIntra(
+ ref Av1PartitionInfo partitionInfo,
+ Av1Plane plane,
+ Av1TileInfo tileInfo,
+ Span referenceBuffer,
+ int referenceStride,
+ Span destination,
+ int destinationStride,
+ Av1BitDepth bitDepth)
+ => this.DecodeInterIntraCore(
+ ref partitionInfo,
+ plane,
+ tileInfo,
+ referenceBuffer,
+ referenceStride,
+ destination,
+ destinationStride,
+ bitDepth);
+
+ ///
+ /// Builds the intra predictor for a high-bit-depth inter-intra plane block in separate caller-owned storage.
+ ///
+ public void DecodeInterIntra(
+ ref Av1PartitionInfo partitionInfo,
+ Av1Plane plane,
+ Av1TileInfo tileInfo,
+ Span referenceBuffer,
+ int referenceStride,
+ Span destination,
+ int destinationStride,
+ Av1BitDepth bitDepth)
+ => this.DecodeInterIntraCore(
+ ref partitionInfo,
+ plane,
+ tileInfo,
+ referenceBuffer,
+ referenceStride,
+ destination,
+ destinationStride,
+ bitDepth);
+
+ ///
+ /// Builds an inter-intra predictor from reconstructed frame neighbors without replacing those references.
+ ///
+ private void DecodeInterIntraCore(
+ ref Av1PartitionInfo partitionInfo,
+ Av1Plane plane,
+ Av1TileInfo tileInfo,
+ Span referenceBuffer,
+ int referenceStride,
+ Span destination,
+ int destinationStride,
+ Av1BitDepth bitDepth)
+ where T : unmanaged, IBinaryInteger
+ {
+ ObuColorConfig colorConfig = this.sequenceHeader.ColorConfig;
+ int subX = plane != Av1Plane.Y && colorConfig.SubSamplingX ? 1 : 0;
+ int subY = plane != Av1Plane.Y && colorConfig.SubSamplingY ? 1 : 0;
+ Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY);
+ Av1TransformSize transformSize = planeBlockSize.GetMaximumTransformSize();
+ Av1PredictionMode mode = partitionInfo.ModeInfo.InterIntraMode switch
+ {
+ Av1InterIntraMode.Vertical => Av1PredictionMode.Vertical,
+ Av1InterIntraMode.Horizontal => Av1PredictionMode.Horizontal,
+ Av1InterIntraMode.Smooth => Av1PredictionMode.Smooth,
+ _ => Av1PredictionMode.DC,
+ };
+
+ Span topNeighbor = referenceBuffer;
+ ReadOnlySpan leftNeighbor = referenceBuffer[(referenceStride - 1)..];
+
+ // Libaom predicts one maximum-transform-sized plane block for inter-intra. Destination storage is separate
+ // because the inter predictor must remain intact until the final mask blend consumes both complete blocks.
+ this.PredictIntraBlock(
+ ref partitionInfo,
+ plane,
+ transformSize,
+ tileInfo,
+ destination,
+ destinationStride,
+ topNeighbor,
+ leftNeighbor,
+ referenceStride,
+ mode,
+ blockModeInfoColumnOffset: 0,
+ blockModeInfoRowOffset: 0,
+ bitDepth);
+ }
+
///
/// Reconstructs a 10-bit or 12-bit intra-predicted transform block.
///
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundDistanceWeights.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundDistanceWeights.cs
new file mode 100644
index 0000000000..d4338bd283
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundDistanceWeights.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter;
+
+///
+/// Derives AV1 compound weights from retained reference display distances.
+///
+internal static class Av1CompoundDistanceWeights
+{
+ private const int MaximumFrameDistance = 31;
+
+ ///
+ /// Derives the weights applied to the first and second predictors.
+ ///
+ public static void Derive(
+ ObuOrderHintInfo orderHintInfo,
+ ObuFrameHeader frameHeader,
+ Av1ReferenceFrameType firstReference,
+ Av1ReferenceFrameType secondReference,
+ out int firstWeight,
+ out int secondWeight)
+ {
+ ReadOnlySpan quantizedDistanceWeights = [2, 3, 2, 5, 2, 7, 1, MaximumFrameDistance];
+ ReadOnlySpan quantizedDistanceLookup = [9, 7, 11, 5, 12, 4, 13, 3];
+ ReadOnlySpan referenceFrameIndices = frameHeader.GetReferenceFrameIndices();
+ ReadOnlySpan referenceOrderHints = frameHeader.GetReferenceOrderHints();
+ int firstCanonicalIndex = (int)firstReference - (int)Av1ReferenceFrameType.Last;
+ int secondCanonicalIndex = (int)secondReference - (int)Av1ReferenceFrameType.Last;
+ uint firstOrderHint = referenceOrderHints[(int)referenceFrameIndices[firstCanonicalIndex]];
+ uint secondOrderHint = referenceOrderHints[(int)referenceFrameIndices[secondCanonicalIndex]];
+ int secondDistance = Av1Math.Clip3(
+ 0,
+ MaximumFrameDistance,
+ Math.Abs(orderHintInfo.GetRelativeDistance(secondOrderHint, frameHeader.OrderHint)));
+
+ int firstDistance = Av1Math.Clip3(
+ 0,
+ MaximumFrameDistance,
+ Math.Abs(orderHintInfo.GetRelativeDistance(frameHeader.OrderHint, firstOrderHint)));
+
+ int order = secondDistance <= firstDistance ? 1 : 0;
+ int weightClass = 3;
+ if (secondDistance != 0 && firstDistance != 0)
+ {
+ for (weightClass = 0; weightClass < 3; weightClass++)
+ {
+ int secondScaledDistance = secondDistance * quantizedDistanceWeights[(weightClass * 2) + order];
+ int firstScaledDistance = firstDistance * quantizedDistanceWeights[(weightClass * 2) + (1 - order)];
+ if ((secondDistance > firstDistance && secondScaledDistance < firstScaledDistance) ||
+ (secondDistance <= firstDistance && secondScaledDistance > firstScaledDistance))
+ {
+ break;
+ }
+ }
+ }
+
+ firstWeight = quantizedDistanceLookup[(weightClass * 2) + order];
+ secondWeight = quantizedDistanceLookup[(weightClass * 2) + (1 - order)];
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.Blending.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.Blending.cs
new file mode 100644
index 0000000000..e61a3d86e8
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.Blending.cs
@@ -0,0 +1,501 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter;
+
+///
+/// Provides distance-weighted and per-sample masked compound blending.
+///
+internal static partial class Av1CompoundInterPredictor
+{
+ private const int DistanceWeightBits = 4;
+ private const int MaskWeightBits = 6;
+ private const int MaximumMaskAlpha = 1 << MaskWeightBits;
+
+ ///
+ /// Combines two 8-bit predictors with AV1 display-distance weights.
+ ///
+ public static void DistanceWeighted(
+ Span destination,
+ int destinationStride,
+ ReadOnlySpan second,
+ int secondStride,
+ int width,
+ int height,
+ int firstWeight,
+ int secondWeight)
+ {
+ for (int row = 0; row < height; row++)
+ {
+ Span destinationRow = destination.Slice(row * destinationStride, width);
+ ReadOnlySpan secondRow = second.Slice(row * secondStride, width);
+ ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow);
+ ref byte secondReference = ref MemoryMarshal.GetReference(secondRow);
+ int column = 0;
+
+ if (Vector512.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector512.Count;
+ for (; column <= vectorEnd; column += Vector512.Count)
+ {
+ Vector512 firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector512 secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column);
+ DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ if (Vector256.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector256.Count;
+ for (; column <= vectorEnd; column += Vector256.Count)
+ {
+ Vector256 firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector256 secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column);
+ DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector128.Count;
+ for (; column <= vectorEnd; column += Vector128.Count)
+ {
+ Vector128 firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector128 secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column);
+ DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ for (; column < width; column++)
+ {
+ destinationRow[column] = (byte)(((destinationRow[column] * firstWeight) + (secondRow[column] * secondWeight) + 8) >> DistanceWeightBits);
+ }
+ }
+ }
+
+ ///
+ /// Combines two high-bit-depth predictors with AV1 display-distance weights.
+ ///
+ public static void DistanceWeighted(
+ Span destination,
+ int destinationStride,
+ ReadOnlySpan second,
+ int secondStride,
+ int width,
+ int height,
+ int firstWeight,
+ int secondWeight)
+ {
+ for (int row = 0; row < height; row++)
+ {
+ Span destinationRow = destination.Slice(row * destinationStride, width);
+ ReadOnlySpan secondRow = second.Slice(row * secondStride, width);
+ ref ushort destinationReference = ref MemoryMarshal.GetReference(destinationRow);
+ ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow);
+ int column = 0;
+
+ if (Vector512.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector512.Count;
+ for (; column <= vectorEnd; column += Vector512.Count)
+ {
+ Vector512 firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector512 secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column);
+ DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ if (Vector256.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector256.Count;
+ for (; column <= vectorEnd; column += Vector256.Count)
+ {
+ Vector256 firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector256 secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column);
+ DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector128.Count;
+ for (; column <= vectorEnd; column += Vector128.Count)
+ {
+ Vector128 firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector128 secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column);
+ DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ for (; column < width; column++)
+ {
+ destinationRow[column] = (ushort)(((destinationRow[column] * firstWeight) + (secondRow[column] * secondWeight) + 8) >> DistanceWeightBits);
+ }
+ }
+ }
+
+ ///
+ /// Blends two 8-bit predictors through a contiguous AV1 alpha mask.
+ ///
+ public static void Blend(
+ Span destination,
+ int destinationStride,
+ ReadOnlySpan second,
+ int secondStride,
+ ReadOnlySpan mask,
+ int maskStride,
+ int width,
+ int height)
+ {
+ for (int row = 0; row < height; row++)
+ {
+ Span destinationRow = destination.Slice(row * destinationStride, width);
+ ReadOnlySpan secondRow = second.Slice(row * secondStride, width);
+ ReadOnlySpan maskRow = mask.Slice(row * maskStride, width);
+ ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow);
+ ref byte secondReference = ref MemoryMarshal.GetReference(secondRow);
+ ref byte maskReference = ref MemoryMarshal.GetReference(maskRow);
+ int column = 0;
+
+ if (Vector512.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector512.Count;
+ for (; column <= vectorEnd; column += Vector512.Count)
+ {
+ Vector512 firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector512 secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column);
+ Vector512 maskVector = Vector512.LoadUnsafe(ref maskReference, (nuint)column);
+ Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ if (Vector256.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector256.Count;
+ for (; column <= vectorEnd; column += Vector256.Count)
+ {
+ Vector256 firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector256 secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column);
+ Vector256 maskVector = Vector256.LoadUnsafe(ref maskReference, (nuint)column);
+ Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector128.Count;
+ for (; column <= vectorEnd; column += Vector128.Count)
+ {
+ Vector128 firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector128 secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column);
+ Vector128 maskVector = Vector128.LoadUnsafe(ref maskReference, (nuint)column);
+ Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ for (; column < width; column++)
+ {
+ int alpha = maskRow[column];
+ destinationRow[column] = (byte)(((alpha * destinationRow[column]) + ((MaximumMaskAlpha - alpha) * secondRow[column]) + 32) >> MaskWeightBits);
+ }
+ }
+ }
+
+ ///
+ /// Blends two high-bit-depth predictors through a contiguous AV1 alpha mask.
+ ///
+ public static void Blend(
+ Span destination,
+ int destinationStride,
+ ReadOnlySpan second,
+ int secondStride,
+ ReadOnlySpan mask,
+ int maskStride,
+ int width,
+ int height)
+ {
+ for (int row = 0; row < height; row++)
+ {
+ Span destinationRow = destination.Slice(row * destinationStride, width);
+ ReadOnlySpan secondRow = second.Slice(row * secondStride, width);
+ ReadOnlySpan maskRow = mask.Slice(row * maskStride, width);
+ ref ushort destinationReference = ref MemoryMarshal.GetReference(destinationRow);
+ ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow);
+ ref byte maskReference = ref MemoryMarshal.GetReference(maskRow);
+ int column = 0;
+
+ if (Vector512.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector512.Count;
+ for (; column <= vectorEnd; column += Vector512.Count)
+ {
+ Vector512 firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector512 secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column);
+ Vector512 maskVector = LoadMask512(ref maskReference, column);
+ Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ if (Vector256.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector256.Count;
+ for (; column <= vectorEnd; column += Vector256.Count)
+ {
+ Vector256 firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector256 secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column);
+ Vector256 maskVector = LoadMask256(ref maskReference, column);
+ Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int vectorEnd = width - Vector128.Count;
+ for (; column <= vectorEnd; column += Vector128.Count)
+ {
+ Vector128 firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column);
+ Vector128 secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column);
+ Vector128 maskVector = LoadMask128(ref maskReference, column);
+ Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column);
+ }
+ }
+
+ for (; column < width; column++)
+ {
+ int alpha = maskRow[column];
+ destinationRow[column] = (ushort)(((alpha * destinationRow[column]) + ((MaximumMaskAlpha - alpha) * secondRow[column]) + 32) >> MaskWeightBits);
+ }
+ }
+ }
+
+ ///
+ /// Combines two 8-bit predictors with display-distance weights without explicit hardware intrinsics.
+ ///
+ public static void DistanceWeightedScalar(
+ Span destination,
+ int destinationStride,
+ ReadOnlySpan second,
+ int secondStride,
+ int width,
+ int height,
+ int firstWeight,
+ int secondWeight)
+ {
+ for (int row = 0; row < height; row++)
+ {
+ Span destinationRow = destination.Slice(row * destinationStride, width);
+ ReadOnlySpan secondRow = second.Slice(row * secondStride, width);
+ for (int column = 0; column < width; column++)
+ {
+ destinationRow[column] = (byte)(((destinationRow[column] * firstWeight) + (secondRow[column] * secondWeight) + 8) >> DistanceWeightBits);
+ }
+ }
+ }
+
+ ///
+ /// Blends two 8-bit predictors through an alpha mask without explicit hardware intrinsics.
+ ///
+ public static void BlendScalar(
+ Span destination,
+ int destinationStride,
+ ReadOnlySpan second,
+ int secondStride,
+ ReadOnlySpan mask,
+ int maskStride,
+ int width,
+ int height)
+ {
+ for (int row = 0; row < height; row++)
+ {
+ Span destinationRow = destination.Slice(row * destinationStride, width);
+ ReadOnlySpan secondRow = second.Slice(row * secondStride, width);
+ ReadOnlySpan maskRow = mask.Slice(row * maskStride, width);
+ for (int column = 0; column < width; column++)
+ {
+ int alpha = maskRow[column];
+ destinationRow[column] = (byte)(((alpha * destinationRow[column]) + ((MaximumMaskAlpha - alpha) * secondRow[column]) + 32) >> MaskWeightBits);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 DistanceWeighted(Vector128 first, Vector128 second, int firstWeight, int secondWeight)
+ {
+ Av1IntraPredictorBase.Widen(first, out Vector128 first0, out Vector128 first1, out Vector128 first2, out Vector128 first3);
+ Av1IntraPredictorBase.Widen(second, out Vector128 second0, out Vector128 second1, out Vector128 second2, out Vector128 second3);
+ return Av1IntraPredictorBase.Narrow(
+ DistanceWeighted(first0, second0, firstWeight, secondWeight),
+ DistanceWeighted(first1, second1, firstWeight, secondWeight),
+ DistanceWeighted(first2, second2, firstWeight, secondWeight),
+ DistanceWeighted(first3, second3, firstWeight, secondWeight));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 DistanceWeighted(Vector256 first, Vector256 second, int firstWeight, int secondWeight)
+ {
+ Av1IntraPredictorBase.Widen(first, out Vector256 first0, out Vector256 first1, out Vector256 first2, out Vector256 first3);
+ Av1IntraPredictorBase.Widen(second, out Vector256 second0, out Vector256 second1, out Vector256 second2, out Vector256 second3);
+ return Av1IntraPredictorBase.Narrow(
+ DistanceWeighted(first0, second0, firstWeight, secondWeight),
+ DistanceWeighted(first1, second1, firstWeight, secondWeight),
+ DistanceWeighted(first2, second2, firstWeight, secondWeight),
+ DistanceWeighted(first3, second3, firstWeight, secondWeight));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector512 DistanceWeighted(Vector512 first, Vector512 second, int firstWeight, int secondWeight)
+ {
+ Av1IntraPredictorBase.Widen(first, out Vector512 first0, out Vector512 first1, out Vector512 first2, out Vector512 first3);
+ Av1IntraPredictorBase.Widen(second, out Vector512 second0, out Vector512 second1, out Vector512 second2, out Vector512 second3);
+ return Av1IntraPredictorBase.Narrow(
+ DistanceWeighted(first0, second0, firstWeight, secondWeight),
+ DistanceWeighted(first1, second1, firstWeight, secondWeight),
+ DistanceWeighted(first2, second2, firstWeight, secondWeight),
+ DistanceWeighted(first3, second3, firstWeight, secondWeight));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 DistanceWeighted(Vector128 first, Vector128 second, int firstWeight, int secondWeight)
+ {
+ Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector128 first0, out Vector128 first1);
+ Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector128 second0, out Vector128 second1);
+ return Av1IntraPredictorBase.Narrow(
+ DistanceWeighted(first0, second0, firstWeight, secondWeight),
+ DistanceWeighted(first1, second1, firstWeight, secondWeight)).AsUInt16();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 DistanceWeighted(Vector256 first, Vector256 second, int firstWeight, int secondWeight)
+ {
+ Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector256 first0, out Vector256 first1);
+ Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector256 second0, out Vector256 second1);
+ return Av1IntraPredictorBase.Narrow(
+ DistanceWeighted(first0, second0, firstWeight, secondWeight),
+ DistanceWeighted(first1, second1, firstWeight, secondWeight)).AsUInt16();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector512 DistanceWeighted(Vector512 first, Vector512 second, int firstWeight, int secondWeight)
+ {
+ Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector512 first0, out Vector512 first1);
+ Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector512 second0, out Vector512 second1);
+ return Av1IntraPredictorBase.Narrow(
+ DistanceWeighted(first0, second0, firstWeight, secondWeight),
+ DistanceWeighted(first1, second1, firstWeight, secondWeight)).AsUInt16();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 DistanceWeighted(Vector128 first, Vector128 second, int firstWeight, int secondWeight)
+ => ((first * Vector128.Create(firstWeight)) + (second * Vector128.Create(secondWeight)) + Vector128.Create(8)) >> DistanceWeightBits;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 DistanceWeighted(Vector256 first, Vector256 second, int firstWeight, int secondWeight)
+ => ((first * Vector256.Create(firstWeight)) + (second * Vector256.Create(secondWeight)) + Vector256.Create(8)) >> DistanceWeightBits;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector512 DistanceWeighted(Vector512 first, Vector512 second, int firstWeight, int secondWeight)
+ => ((first * Vector512.Create(firstWeight)) + (second * Vector512.Create(secondWeight)) + Vector512.Create(8)) >> DistanceWeightBits;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 Blend(Vector128 first, Vector128 second, Vector128 mask)
+ {
+ Av1IntraPredictorBase.Widen(first, out Vector128 first0, out Vector128 first1, out Vector128 first2, out Vector128 first3);
+ Av1IntraPredictorBase.Widen(second, out Vector128 second0, out Vector128 second1, out Vector128 second2, out Vector128 second3);
+ Av1IntraPredictorBase.Widen(mask, out Vector128 mask0, out Vector128 mask1, out Vector128 mask2, out Vector128 mask3);
+ return Av1IntraPredictorBase.Narrow(
+ Blend(first0, second0, mask0),
+ Blend(first1, second1, mask1),
+ Blend(first2, second2, mask2),
+ Blend(first3, second3, mask3));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 Blend(Vector256 first, Vector256 second, Vector256 mask)
+ {
+ Av1IntraPredictorBase.Widen(first, out Vector256 first0, out Vector256 first1, out Vector256 first2, out Vector256 first3);
+ Av1IntraPredictorBase.Widen(second, out Vector256 second0, out Vector256 second1, out Vector256 second2, out Vector256 second3);
+ Av1IntraPredictorBase.Widen(mask, out Vector256 mask0, out Vector256 mask1, out Vector256 mask2, out Vector256 mask3);
+ return Av1IntraPredictorBase.Narrow(
+ Blend(first0, second0, mask0),
+ Blend(first1, second1, mask1),
+ Blend(first2, second2, mask2),
+ Blend(first3, second3, mask3));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector512 Blend(Vector512 first, Vector512 second, Vector512 mask)
+ {
+ Av1IntraPredictorBase.Widen(first, out Vector512 first0, out Vector512 first1, out Vector512 first2, out Vector512 first3);
+ Av1IntraPredictorBase.Widen(second, out Vector512 second0, out Vector512 second1, out Vector512 second2, out Vector512 second3);
+ Av1IntraPredictorBase.Widen(mask, out Vector512 mask0, out Vector512 mask1, out Vector512 mask2, out Vector512 mask3);
+ return Av1IntraPredictorBase.Narrow(
+ Blend(first0, second0, mask0),
+ Blend(first1, second1, mask1),
+ Blend(first2, second2, mask2),
+ Blend(first3, second3, mask3));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 Blend(Vector128 first, Vector128 second, Vector128 mask)
+ {
+ Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector128 first0, out Vector128 first1);
+ Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector128 second0, out Vector128 second1);
+ Av1IntraPredictorBase.Widen(mask.AsInt16(), out Vector128 mask0, out Vector128 mask1);
+ return Av1IntraPredictorBase.Narrow(Blend(first0, second0, mask0), Blend(first1, second1, mask1)).AsUInt16();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 Blend(Vector256 first, Vector256 second, Vector256 mask)
+ {
+ Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector256 first0, out Vector256 first1);
+ Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector256 second0, out Vector256 second1);
+ Av1IntraPredictorBase.Widen(mask.AsInt16(), out Vector256 mask0, out Vector256 mask1);
+ return Av1IntraPredictorBase.Narrow(Blend(first0, second0, mask0), Blend(first1, second1, mask1)).AsUInt16();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector512 Blend(Vector512 first, Vector512 second, Vector512 mask)
+ {
+ Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector512 first0, out Vector512 first1);
+ Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector512 second0, out Vector512 second1);
+ Av1IntraPredictorBase.Widen(mask.AsInt16(), out Vector512 mask0, out Vector512 mask1);
+ return Av1IntraPredictorBase.Narrow(Blend(first0, second0, mask0), Blend(first1, second1, mask1)).AsUInt16();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 Blend(Vector128 first, Vector128 second, Vector128 mask)
+ => ((mask * first) + ((Vector128.Create(MaximumMaskAlpha) - mask) * second) + Vector128.Create(32)) >> MaskWeightBits;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 Blend(Vector256 first, Vector256 second, Vector256 mask)
+ => ((mask * first) + ((Vector256.Create(MaximumMaskAlpha) - mask) * second) + Vector256.Create(32)) >> MaskWeightBits;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector512 Blend(Vector512 first, Vector512 second, Vector512 mask)
+ => ((mask * first) + ((Vector512.Create(MaximumMaskAlpha) - mask) * second) + Vector512.Create(32)) >> MaskWeightBits;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 LoadMask128(ref byte source, int offset)
+ {
+ Vector64