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 packed = Unsafe.As>(ref Unsafe.Add(ref source, offset)); + return Vector128.WidenLower(Vector128.Create(packed, Vector64.Zero)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 LoadMask256(ref byte source, int offset) + { + Vector128 packed = Vector128.LoadUnsafe(ref source, (nuint)offset); + return Vector256.WidenLower(Vector256.Create(packed, Vector128.Zero)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 LoadMask512(ref byte source, int offset) + { + Vector256 packed = Vector256.LoadUnsafe(ref source, (nuint)offset); + return Vector512.WidenLower(Vector512.Create(packed, Vector256.Zero)); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.Masks.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.Masks.cs new file mode 100644 index 0000000000..d1224cf3dc --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.Masks.cs @@ -0,0 +1,294 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; + +/// +/// Produces the smooth inter-intra and predictor-difference masks used by compound blending. +/// +internal static partial class Av1CompoundInterPredictor +{ + /// + /// Gets libaom's one-dimensional inter-intra alpha curve. + /// + private static ReadOnlySpan InterIntraWeights => + [ + 60, 58, 56, 54, 52, 50, 48, 47, 45, 44, 42, 41, 39, 38, 37, 35, + 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 22, 21, 20, + 19, 19, 18, 18, 17, 16, 16, 15, 15, 14, 14, 13, 13, 12, 12, 12, + 11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, + 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ]; + + /// + /// Fills a smooth inter-intra mask for one plane. + /// + public static void FillInterIntraMask( + Span mask, + int maskStride, + int width, + int height, + Av1InterIntraMode mode, + bool invert) + { + int sizeScale = 128 / Math.Max(width, height); + for (int row = 0; row < height; row++) + { + Span maskRow = mask.Slice(row * maskStride, width); + for (int column = 0; column < width; column++) + { + int alpha = mode switch + { + Av1InterIntraMode.Vertical => InterIntraWeights[row * sizeScale], + Av1InterIntraMode.Horizontal => InterIntraWeights[column * sizeScale], + Av1InterIntraMode.Smooth => InterIntraWeights[Math.Min(row, column) * sizeScale], + _ => 32, + }; + + maskRow[column] = (byte)(invert ? MaximumMaskAlpha - alpha : alpha); + } + } + } + + /// + /// Fills an 8-bit difference-weighted compound mask. + /// + public static void FillDifferenceWeightedMask( + Span mask, + int maskStride, + ReadOnlySpan first, + int firstStride, + ReadOnlySpan second, + int secondStride, + int width, + int height, + Av1DifferenceWeightedMaskType maskType) + { + bool invert = maskType == Av1DifferenceWeightedMaskType.Type38Inverse; + for (int row = 0; row < height; row++) + { + Span maskRow = mask.Slice(row * maskStride, width); + ReadOnlySpan firstRow = first.Slice(row * firstStride, width); + ReadOnlySpan secondRow = second.Slice(row * secondStride, width); + ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); + ref byte firstReference = ref MemoryMarshal.GetReference(firstRow); + 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 firstReference, (nuint)column); + Vector512 secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); + DifferenceWeighted(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int vectorEnd = width - Vector256.Count; + for (; column <= vectorEnd; column += Vector256.Count) + { + Vector256 firstVector = Vector256.LoadUnsafe(ref firstReference, (nuint)column); + Vector256 secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); + DifferenceWeighted(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); + } + } + + if (Vector128.IsHardwareAccelerated) + { + int vectorEnd = width - Vector128.Count; + for (; column <= vectorEnd; column += Vector128.Count) + { + Vector128 firstVector = Vector128.LoadUnsafe(ref firstReference, (nuint)column); + Vector128 secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); + DifferenceWeighted(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); + } + } + + for (; column < width; column++) + { + int difference = Math.Abs(firstRow[column] - secondRow[column]) >> 4; + int alpha = Math.Min(MaximumMaskAlpha, 38 + difference); + maskRow[column] = (byte)(invert ? MaximumMaskAlpha - alpha : alpha); + } + } + } + + /// + /// Fills a high-bit-depth difference-weighted compound mask. + /// + public static void FillDifferenceWeightedMask( + Span mask, + int maskStride, + ReadOnlySpan first, + int firstStride, + ReadOnlySpan second, + int secondStride, + int width, + int height, + int bitDepth, + Av1DifferenceWeightedMaskType maskType) + { + bool invert = maskType == Av1DifferenceWeightedMaskType.Type38Inverse; + int differenceShift = bitDepth - 8 + 4; + for (int row = 0; row < height; row++) + { + Span maskRow = mask.Slice(row * maskStride, width); + ReadOnlySpan firstRow = first.Slice(row * firstStride, width); + ReadOnlySpan secondRow = second.Slice(row * secondStride, width); + ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); + ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); + ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); + int column = 0; + + // Two input vectors narrow to one packed byte mask. This keeps mask construction contiguous and avoids + // temporary buffers before the following vector blend consumes the complete plane block. + if (Vector512.IsHardwareAccelerated) + { + int vectorEnd = width - Vector512.Count; + for (; column <= vectorEnd; column += Vector512.Count) + { + Vector512 first0 = Vector512.LoadUnsafe(ref firstReference, (nuint)column); + Vector512 first1 = Vector512.LoadUnsafe(ref firstReference, (nuint)(column + Vector512.Count)); + Vector512 second0 = Vector512.LoadUnsafe(ref secondReference, (nuint)column); + Vector512 second1 = Vector512.LoadUnsafe(ref secondReference, (nuint)(column + Vector512.Count)); + DifferenceWeighted(first0, first1, second0, second1, differenceShift, invert) + .StoreUnsafe(ref maskReference, (nuint)column); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int vectorEnd = width - Vector256.Count; + for (; column <= vectorEnd; column += Vector256.Count) + { + Vector256 first0 = Vector256.LoadUnsafe(ref firstReference, (nuint)column); + Vector256 first1 = Vector256.LoadUnsafe(ref firstReference, (nuint)(column + Vector256.Count)); + Vector256 second0 = Vector256.LoadUnsafe(ref secondReference, (nuint)column); + Vector256 second1 = Vector256.LoadUnsafe(ref secondReference, (nuint)(column + Vector256.Count)); + DifferenceWeighted(first0, first1, second0, second1, differenceShift, invert) + .StoreUnsafe(ref maskReference, (nuint)column); + } + } + + if (Vector128.IsHardwareAccelerated) + { + int vectorEnd = width - Vector128.Count; + for (; column <= vectorEnd; column += Vector128.Count) + { + Vector128 first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); + Vector128 first1 = Vector128.LoadUnsafe(ref firstReference, (nuint)(column + Vector128.Count)); + Vector128 second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); + Vector128 second1 = Vector128.LoadUnsafe(ref secondReference, (nuint)(column + Vector128.Count)); + DifferenceWeighted(first0, first1, second0, second1, differenceShift, invert) + .StoreUnsafe(ref maskReference, (nuint)column); + } + } + + for (; column < width; column++) + { + int difference = Math.Abs(firstRow[column] - secondRow[column]) >> differenceShift; + int alpha = Math.Min(MaximumMaskAlpha, 38 + difference); + maskRow[column] = (byte)(invert ? MaximumMaskAlpha - alpha : alpha); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 DifferenceWeighted(Vector128 first, Vector128 second, int shift, bool invert) + { + Vector128 difference = Vector128.Max(first, second) - Vector128.Min(first, second); + Vector128 lower = DifferenceWeightedAlpha(Vector128.WidenLower(difference), shift, invert); + Vector128 upper = DifferenceWeightedAlpha(Vector128.WidenUpper(difference), shift, invert); + return Vector128.Narrow(lower, upper); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 DifferenceWeighted(Vector256 first, Vector256 second, int shift, bool invert) + { + Vector256 difference = Vector256.Max(first, second) - Vector256.Min(first, second); + Vector256 lower = DifferenceWeightedAlpha(Vector256.WidenLower(difference), shift, invert); + Vector256 upper = DifferenceWeightedAlpha(Vector256.WidenUpper(difference), shift, invert); + return Vector256.Narrow(lower, upper); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 DifferenceWeighted(Vector512 first, Vector512 second, int shift, bool invert) + { + Vector512 difference = Vector512.Max(first, second) - Vector512.Min(first, second); + Vector512 lower = DifferenceWeightedAlpha(Vector512.WidenLower(difference), shift, invert); + Vector512 upper = DifferenceWeightedAlpha(Vector512.WidenUpper(difference), shift, invert); + return Vector512.Narrow(lower, upper); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 DifferenceWeighted( + Vector128 first0, + Vector128 first1, + Vector128 second0, + Vector128 second1, + int shift, + bool invert) + => Vector128.Narrow( + DifferenceWeightedAlpha(Vector128.Max(first0, second0) - Vector128.Min(first0, second0), shift, invert), + DifferenceWeightedAlpha(Vector128.Max(first1, second1) - Vector128.Min(first1, second1), shift, invert)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 DifferenceWeighted( + Vector256 first0, + Vector256 first1, + Vector256 second0, + Vector256 second1, + int shift, + bool invert) + => Vector256.Narrow( + DifferenceWeightedAlpha(Vector256.Max(first0, second0) - Vector256.Min(first0, second0), shift, invert), + DifferenceWeightedAlpha(Vector256.Max(first1, second1) - Vector256.Min(first1, second1), shift, invert)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 DifferenceWeighted( + Vector512 first0, + Vector512 first1, + Vector512 second0, + Vector512 second1, + int shift, + bool invert) + => Vector512.Narrow( + DifferenceWeightedAlpha(Vector512.Max(first0, second0) - Vector512.Min(first0, second0), shift, invert), + DifferenceWeightedAlpha(Vector512.Max(first1, second1) - Vector512.Min(first1, second1), shift, invert)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 DifferenceWeightedAlpha(Vector128 difference, int shift, bool invert) + { + Vector128 maximum = Vector128.Create((ushort)MaximumMaskAlpha); + Vector128 alpha = Vector128.Min(maximum, (difference >> shift) + Vector128.Create((ushort)38)); + return invert ? maximum - alpha : alpha; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 DifferenceWeightedAlpha(Vector256 difference, int shift, bool invert) + { + Vector256 maximum = Vector256.Create((ushort)MaximumMaskAlpha); + Vector256 alpha = Vector256.Min(maximum, (difference >> shift) + Vector256.Create((ushort)38)); + return invert ? maximum - alpha : alpha; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 DifferenceWeightedAlpha(Vector512 difference, int shift, bool invert) + { + Vector512 maximum = Vector512.Create((ushort)MaximumMaskAlpha); + Vector512 alpha = Vector512.Min(maximum, (difference >> shift) + Vector512.Create((ushort)38)); + return invert ? maximum - alpha : alpha; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.cs index e3365bd034..25685890a5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; /// /// Combines two AV1 inter predictors with equal-weight rounded averaging. /// -internal static class Av1CompoundInterPredictor +internal static partial class Av1CompoundInterPredictor { /// /// Averages an 8-bit predictor into an existing prediction block. diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1WedgeMask.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1WedgeMask.cs new file mode 100644 index 0000000000..0d4654a1c4 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1WedgeMask.cs @@ -0,0 +1,204 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; + +/// +/// Produces AV1 wedge masks in caller-owned plane-sized storage. +/// +internal static class Av1WedgeMask +{ + private const int MaximumAlpha = 64; + private const int MasterSize = 64; + + /// + /// Gets the odd-row oblique prototype from pinned libaom. + /// + private static ReadOnlySpan MasterObliqueOdd => + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 6, 18, + 37, 53, 60, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + ]; + + /// + /// Gets the even-row oblique prototype from pinned libaom. + /// + private static ReadOnlySpan MasterObliqueEven => + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 11, 27, + 46, 58, 62, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + ]; + + /// + /// Gets the vertical prototype from pinned libaom. + /// + private static ReadOnlySpan MasterVertical => + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 7, 21, + 43, 57, 62, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + ]; + + /// + /// Gets the codebook used when block height exceeds block width. + /// + private static ReadOnlySpan HeightGreaterCodebook => + [ + 2, 4, 4, 3, 4, 4, 4, 4, 4, 5, 4, 4, + 0, 4, 2, 0, 4, 4, 0, 4, 6, 1, 4, 4, + 2, 4, 2, 2, 4, 6, 5, 4, 2, 5, 4, 6, + 3, 2, 4, 3, 6, 4, 4, 2, 4, 4, 6, 4, + ]; + + /// + /// Gets the codebook used when block width exceeds block height. + /// + private static ReadOnlySpan HeightLessCodebook => + [ + 2, 4, 4, 3, 4, 4, 4, 4, 4, 5, 4, 4, + 1, 2, 4, 1, 4, 4, 1, 6, 4, 0, 4, 4, + 2, 4, 2, 2, 4, 6, 5, 4, 2, 5, 4, 6, + 3, 2, 4, 3, 6, 4, 4, 2, 4, 4, 6, 4, + ]; + + /// + /// Gets the codebook used by square blocks. + /// + private static ReadOnlySpan EqualCodebook => + [ + 2, 4, 4, 3, 4, 4, 4, 4, 4, 5, 4, 4, + 0, 4, 2, 0, 4, 6, 1, 2, 4, 1, 6, 4, + 2, 4, 2, 2, 4, 6, 5, 4, 2, 5, 4, 6, + 3, 2, 4, 3, 6, 4, 4, 2, 4, 4, 6, 4, + ]; + + /// + /// Fills one luma or subsampled chroma mask for a selected wedge. + /// + /// The caller-owned plane mask. + /// The distance between destination rows. + /// The luma block size selecting the wedge codebook. + /// The wedge index in the inclusive range zero through fifteen. + /// The signaled compound wedge orientation. + /// The horizontal plane subsampling shift. + /// The vertical plane subsampling shift. + /// Whether to complement the resulting mask. + public static void Fill( + Span destination, + int destinationStride, + Av1BlockSize blockSize, + int wedgeIndex, + bool wedgeSign, + int subX, + int subY, + bool invert) + { + int lumaWidth = blockSize.GetWidth(); + int lumaHeight = blockSize.GetHeight(); + int width = Math.Max(4, lumaWidth >> subX); + int height = Math.Max(4, lumaHeight >> subY); + ReadOnlySpan codebook = lumaHeight > lumaWidth + ? HeightGreaterCodebook + : lumaHeight < lumaWidth ? HeightLessCodebook : EqualCodebook; + + int codebookOffset = wedgeIndex * 3; + int direction = codebook[codebookOffset]; + int horizontalOffset = (codebook[codebookOffset + 1] * lumaWidth) >> 3; + int verticalOffset = (codebook[codebookOffset + 2] * lumaHeight) >> 3; + bool negative = wedgeSign ^ GetSignFlip(blockSize, wedgeIndex); + int masterRow = (MasterSize / 2) - verticalOffset; + int masterColumn = (MasterSize / 2) - horizontalOffset; + + // Chroma masks are the rounded average of the corresponding two or four luma-mask samples. Producing the + // plane mask once keeps the vector blend contiguous and avoids gathering mask bytes in every SIMD lane. + for (int row = 0; row < height; row++) + { + Span destinationRow = destination.Slice(row * destinationStride, width); + int lumaRow = row << subY; + for (int column = 0; column < width; column++) + { + int lumaColumn = column << subX; + int mask = GetMasterValue(direction, negative, masterRow + lumaRow, masterColumn + lumaColumn); + if (subX != 0) + { + mask += GetMasterValue(direction, negative, masterRow + lumaRow, masterColumn + lumaColumn + 1); + } + + if (subY != 0) + { + int lowerMask = GetMasterValue(direction, negative, masterRow + lumaRow + 1, masterColumn + lumaColumn); + if (subX != 0) + { + lowerMask += GetMasterValue(direction, negative, masterRow + lumaRow + 1, masterColumn + lumaColumn + 1); + } + + mask += lowerMask; + } + + int sampleCountShift = subX + subY; + if (sampleCountShift != 0) + { + mask = (mask + (1 << (sampleCountShift - 1))) >> sampleCountShift; + } + + destinationRow[column] = (byte)(invert ? MaximumAlpha - mask : mask); + } + } + } + + /// + /// Reads one value from the generated 64 by 64 master mask. + /// + private static int GetMasterValue(int direction, bool negative, int row, int column) + { + int value = direction switch + { + 0 => MasterVertical[row], + 1 => MasterVertical[column], + 2 => GetOblique63(column, row), + 3 => GetOblique63(row, column), + 4 => MaximumAlpha - GetOblique63(row, MasterSize - 1 - column), + _ => MaximumAlpha - GetOblique63(column, MasterSize - 1 - row), + }; + + return negative ? MaximumAlpha - value : value; + } + + /// + /// Reads one value from the shifted oblique-63 master prototype. + /// + private static int GetOblique63(int row, int column) + { + bool oddRow = (row & 1) != 0; + int shift = (oddRow ? 15 : 16) - (row >> 1); + int sourceColumn = Av1Math.Clip3(0, MasterSize - 1, column - shift); + return oddRow ? MasterObliqueOdd[sourceColumn] : MasterObliqueEven[sourceColumn]; + } + + /// + /// Gets libaom's canonical sign flip for a block and wedge index. + /// + private static bool GetSignFlip(Av1BlockSize blockSize, int wedgeIndex) + { + ReadOnlySpan signFlips = blockSize switch + { + Av1BlockSize.Block8x8 or Av1BlockSize.Block16x16 or Av1BlockSize.Block32x32 => + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1], + Av1BlockSize.Block8x32 => + [1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1], + Av1BlockSize.Block32x8 => + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1], + _ => + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1], + }; + + return signFlips[wedgeIndex] != 0; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 7b1b91baa9..53b8f51266 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -2019,6 +2019,8 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable } modeInfo.MotionMode = Av1MotionMode.SimpleTranslation; + modeInfo.UseInterIntraWedge = false; + modeInfo.InterIntraWedgeIndex = 0; int minimumBlockDimension = Math.Min(modeInfo.BlockSize.GetWidth(), modeInfo.BlockSize.GetHeight()); if (!isCompound && !modeInfo.SkipMode && @@ -2026,13 +2028,25 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable modeInfo.BlockSize is >= Av1BlockSize.Block8x8 and <= Av1BlockSize.Block32x32 && reader.ReadIsInterIntra(modeInfo.BlockSize)) { - // AV1 assigns this syntax only to the contiguous Block8x8 through Block32x32 enum range; similarly - // dimensioned extended rectangles occur later in the enum and must not consume a flag. A false flag - // continues into motion-mode syntax even while the selected inter-intra predictor remains unsupported. - throw new NotSupportedException("AV1 inter-intra block prediction is not implemented."); + // The synthetic INTRA_FRAME second reference is part of the decoded mode state: it suppresses motion + // variation syntax and lets reconstruction distinguish inter-intra from a regular single-reference block. + modeInfo.ReferenceFrames[1] = Av1ReferenceFrameType.Intra; + modeInfo.InterIntraMode = reader.ReadInterIntraMode(modeInfo.BlockSize); + modeInfo.SetAngleDelta(Av1PlaneType.Y, 0); + modeInfo.SetAngleDelta(Av1PlaneType.Uv, 0); + modeInfo.UseFilterIntra = false; + modeInfo.UseInterIntraWedge = reader.ReadUseInterIntraWedge(modeInfo.BlockSize); + if (modeInfo.UseInterIntraWedge) + { + modeInfo.InterIntraWedgeIndex = reader.ReadWedgeIndex(modeInfo.BlockSize); + } } - if (!isCompound && this.FrameHeader.IsMotionModeSwitchable && minimumBlockDimension >= 8 && !modeInfo.SkipMode) + if (!isCompound && + modeInfo.ReferenceFrames[1] != Av1ReferenceFrameType.Intra && + this.FrameHeader.IsMotionModeSwitchable && + minimumBlockDimension >= 8 && + !modeInfo.SkipMode) { Av1MotionVariationCandidates candidates = this.motionVariationCandidates; candidates.Build(ref partitionInfo, tileInfo, this.SequenceHeader, this.FrameHeader, referenceFrame); @@ -2070,12 +2084,65 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable modeInfo.CompoundGroupIndex = false; modeInfo.CompoundIndex = true; modeInfo.CompoundType = Av1CompoundType.Average; - if (isCompound && !modeInfo.SkipMode && - (this.SequenceHeader.EnableMaskedCompound || this.SequenceHeader.OrderHintInfo.EnableJointCompound)) + modeInfo.CompoundWedgeIndex = 0; + modeInfo.CompoundWedgeSign = false; + modeInfo.DifferenceWeightedMaskType = Av1DifferenceWeightedMaskType.Type38; + if (isCompound && !modeInfo.SkipMode) { - // Either enabled sequence tool adds a compound-selection symbol before interpolation syntax. Refuse - // that later checkpoint at its owning boundary so this equal-average path cannot desynchronize tiles. - throw new NotSupportedException("AV1 selectable compound blending is not implemented."); + bool maskedCompoundUsed = minimumBlockDimension >= 8 && this.SequenceHeader.EnableMaskedCompound; + if (maskedCompoundUsed) + { + int groupContext = Av1SymbolContextHelper.GetCompoundGroupIndexContext( + partitionInfo.AboveModeInfo, + partitionInfo.LeftModeInfo); + + modeInfo.CompoundGroupIndex = reader.ReadCompoundGroupIndex(groupContext); + } + + if (!modeInfo.CompoundGroupIndex) + { + if (this.SequenceHeader.OrderHintInfo.EnableJointCompound) + { + int compoundIndexContext = Av1SymbolContextHelper.GetCompoundIndexContext( + this.SequenceHeader.OrderHintInfo, + this.FrameHeader, + modeInfo, + partitionInfo.AboveModeInfo, + partitionInfo.LeftModeInfo); + + modeInfo.CompoundIndex = reader.ReadCompoundIndex(compoundIndexContext); + modeInfo.CompoundType = modeInfo.CompoundIndex + ? Av1CompoundType.Average + : Av1CompoundType.DistanceWeighted; + } + } + else + { + bool supportsWedge = modeInfo.BlockSize is + Av1BlockSize.Block8x8 or + Av1BlockSize.Block8x16 or + Av1BlockSize.Block16x8 or + Av1BlockSize.Block16x16 or + Av1BlockSize.Block16x32 or + Av1BlockSize.Block32x16 or + Av1BlockSize.Block32x32 or + Av1BlockSize.Block8x32 or + Av1BlockSize.Block32x8; + + modeInfo.CompoundType = supportsWedge + ? reader.ReadMaskedCompoundType(modeInfo.BlockSize) + : Av1CompoundType.DifferenceWeighted; + + if (modeInfo.CompoundType == Av1CompoundType.Wedge) + { + modeInfo.CompoundWedgeIndex = reader.ReadWedgeIndex(modeInfo.BlockSize); + modeInfo.CompoundWedgeSign = reader.ReadLiteral(1) != 0; + } + else + { + modeInfo.DifferenceWeightedMaskType = (Av1DifferenceWeightedMaskType)reader.ReadLiteral(1); + } + } } Span interpolationFilters = modeInfo.InterpolationFilters; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs index 126a7da8ad..3329b812f7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs @@ -139,7 +139,10 @@ internal sealed class Av1BlockDecoder : IDisposable this.inverseQuantizationOwner = inverseQuantizationOwner; this.transformWorkspaceOwner = transformWorkspaceOwner; this.predictionScratchOwner = predictionScratchOwner; - this.predictionDecoder = new(sequenceHeader, frameHeader, predictionScratchOwner.Memory); + this.predictionDecoder = new( + sequenceHeader, + frameHeader, + predictionScratchOwner.Memory[maximumBlockArea..]); this.isLoopFilterEnabled = frameHeader.LoopFilterParameters.FilterLevel[0] != 0 || frameHeader.LoopFilterParameters.FilterLevel[1] != 0; @@ -241,6 +244,9 @@ internal sealed class Av1BlockDecoder : IDisposable Av1FrameBuffer? referenceFrameBuffer = null; Av1FrameBuffer? secondaryReferenceFrameBuffer = null; bool isCompound = modeInfo.ReferenceFrames[1] > Av1ReferenceFrameType.Intra; + bool isInterIntra = modeInfo.ReferenceFrames[1] == Av1ReferenceFrameType.Intra; + int firstCompoundWeight = 8; + int secondCompoundWeight = 8; if (isInterBlock) { int canonicalReferenceIndex = (int)modeInfo.ReferenceFrames[0] - (int)Av1ReferenceFrameType.Last; @@ -270,12 +276,17 @@ internal sealed class Av1BlockDecoder : IDisposable throw new NotSupportedException("AV1 non-translational global prediction is not implemented."); } - if (modeInfo.CompoundType != Av1CompoundType.Average) + secondaryReferenceFrameBuffer = this.ResolveReferenceFrame(modeInfo.ReferenceFrames[1]); + if (modeInfo.CompoundType == Av1CompoundType.DistanceWeighted) { - throw new NotSupportedException($"AV1 {modeInfo.CompoundType} compound prediction is not implemented."); + Av1CompoundDistanceWeights.Derive( + this.sequenceHeader.OrderHintInfo, + this.frameHeader, + modeInfo.ReferenceFrames[0], + modeInfo.ReferenceFrames[1], + out firstCompoundWeight, + out secondCompoundWeight); } - - secondaryReferenceFrameBuffer = this.ResolveReferenceFrame(modeInfo.ReferenceFrames[1]); } } @@ -343,6 +354,7 @@ internal sealed class Av1BlockDecoder : IDisposable Span predictionScratch = predictionStorage[maximumBlockArea..]; Span secondPrediction = MemoryMarshal.AsBytes(secondPredictionStorage)[..(predictionWidth * predictionHeight)]; Span highBitDepthSecondPrediction = MemoryMarshal.Cast(secondPredictionStorage)[..(predictionWidth * predictionHeight)]; + Span compoundMask = MemoryMarshal.AsBytes(predictionScratch)[..(predictionWidth * predictionHeight)]; int referenceCount = isCompound ? 2 : 1; for (int referenceIndex = 0; referenceIndex < referenceCount; referenceIndex++) @@ -455,21 +467,242 @@ internal sealed class Av1BlockDecoder : IDisposable Span destination = MemoryMarshal.Cast( highBitDepthBlockReconstructionBuffer[reconstructionStride..]); - Av1CompoundInterPredictor.Average( - destination, + switch (modeInfo.CompoundType) + { + case Av1CompoundType.Average: + Av1CompoundInterPredictor.Average( + destination, + reconstructionStride, + highBitDepthSecondPrediction, + predictionWidth, + predictionWidth, + predictionHeight); + + break; + case Av1CompoundType.DistanceWeighted: + Av1CompoundInterPredictor.DistanceWeighted( + destination, + reconstructionStride, + highBitDepthSecondPrediction, + predictionWidth, + predictionWidth, + predictionHeight, + firstCompoundWeight, + secondCompoundWeight); + + break; + case Av1CompoundType.Wedge: + Av1WedgeMask.Fill( + compoundMask, + predictionWidth, + blockSize, + modeInfo.CompoundWedgeIndex, + modeInfo.CompoundWedgeSign, + subX, + subY, + invert: false); + + Av1CompoundInterPredictor.Blend( + destination, + reconstructionStride, + highBitDepthSecondPrediction, + predictionWidth, + compoundMask, + predictionWidth, + predictionWidth, + predictionHeight); + + break; + default: + Av1CompoundInterPredictor.FillDifferenceWeightedMask( + compoundMask, + predictionWidth, + destination, + reconstructionStride, + highBitDepthSecondPrediction, + predictionWidth, + predictionWidth, + predictionHeight, + this.frameBuffer.BitDepth.GetBitCount(), + modeInfo.DifferenceWeightedMaskType); + + Av1CompoundInterPredictor.Blend( + destination, + reconstructionStride, + highBitDepthSecondPrediction, + predictionWidth, + compoundMask, + predictionWidth, + predictionWidth, + predictionHeight); + + break; + } + } + else + { + Span destination = blockReconstructionBuffer[reconstructionStride..]; + switch (modeInfo.CompoundType) + { + case Av1CompoundType.Average: + Av1CompoundInterPredictor.Average( + destination, + reconstructionStride, + secondPrediction, + predictionWidth, + predictionWidth, + predictionHeight); + + break; + case Av1CompoundType.DistanceWeighted: + Av1CompoundInterPredictor.DistanceWeighted( + destination, + reconstructionStride, + secondPrediction, + predictionWidth, + predictionWidth, + predictionHeight, + firstCompoundWeight, + secondCompoundWeight); + + break; + case Av1CompoundType.Wedge: + Av1WedgeMask.Fill( + compoundMask, + predictionWidth, + blockSize, + modeInfo.CompoundWedgeIndex, + modeInfo.CompoundWedgeSign, + subX, + subY, + invert: false); + + Av1CompoundInterPredictor.Blend( + destination, + reconstructionStride, + secondPrediction, + predictionWidth, + compoundMask, + predictionWidth, + predictionWidth, + predictionHeight); + + break; + default: + Av1CompoundInterPredictor.FillDifferenceWeightedMask( + compoundMask, + predictionWidth, + destination, + reconstructionStride, + secondPrediction, + predictionWidth, + predictionWidth, + predictionHeight, + modeInfo.DifferenceWeightedMaskType); + + Av1CompoundInterPredictor.Blend( + destination, + reconstructionStride, + secondPrediction, + predictionWidth, + compoundMask, + predictionWidth, + predictionWidth, + predictionHeight); + + break; + } + } + } + else if (isInterIntra) + { + if (highBitDepth) + { + this.predictionDecoder.DecodeInterIntra( + ref partitionInfo, + (Av1Plane)plane, + tileInfo, + highBitDepthBlockReconstructionBuffer, + reconstructionStride, + secondPredictionStorage[..(predictionWidth * predictionHeight)], + predictionWidth, + this.frameBuffer.BitDepth); + + if (modeInfo.UseInterIntraWedge) + { + Av1WedgeMask.Fill( + compoundMask, + predictionWidth, + blockSize, + modeInfo.InterIntraWedgeIndex, + wedgeSign: false, + subX, + subY, + invert: true); + } + else + { + Av1CompoundInterPredictor.FillInterIntraMask( + compoundMask, + predictionWidth, + predictionWidth, + predictionHeight, + modeInfo.InterIntraMode, + invert: true); + } + + Av1CompoundInterPredictor.Blend( + MemoryMarshal.Cast(highBitDepthBlockReconstructionBuffer[reconstructionStride..]), reconstructionStride, highBitDepthSecondPrediction, predictionWidth, + compoundMask, + predictionWidth, predictionWidth, predictionHeight); } else { - Av1CompoundInterPredictor.Average( + this.predictionDecoder.DecodeInterIntra( + ref partitionInfo, + (Av1Plane)plane, + tileInfo, + blockReconstructionBuffer, + reconstructionStride, + secondPrediction, + predictionWidth, + this.frameBuffer.BitDepth); + + if (modeInfo.UseInterIntraWedge) + { + Av1WedgeMask.Fill( + compoundMask, + predictionWidth, + blockSize, + modeInfo.InterIntraWedgeIndex, + wedgeSign: false, + subX, + subY, + invert: true); + } + else + { + Av1CompoundInterPredictor.FillInterIntraMask( + compoundMask, + predictionWidth, + predictionWidth, + predictionHeight, + modeInfo.InterIntraMode, + invert: true); + } + + Av1CompoundInterPredictor.Blend( blockReconstructionBuffer[reconstructionStride..], reconstructionStride, secondPrediction, predictionWidth, + compoundMask, + predictionWidth, predictionWidth, predictionHeight); } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs index 073bf45c17..c18d87dd57 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs @@ -110,6 +110,214 @@ public class Av1CompoundBlockDecoderTests } } + /// + /// Verifies selectable compound reconstruction through the production block branch at every supported bit depth. + /// + /// The native sample depth. + /// The selected compound operation. + [Theory] + [InlineData((int)Av1BitDepth.EightBit, (int)Av1CompoundType.DistanceWeighted)] + [InlineData((int)Av1BitDepth.TenBit, (int)Av1CompoundType.DistanceWeighted)] + [InlineData((int)Av1BitDepth.TwelveBit, (int)Av1CompoundType.DistanceWeighted)] + [InlineData((int)Av1BitDepth.EightBit, (int)Av1CompoundType.Wedge)] + [InlineData((int)Av1BitDepth.TenBit, (int)Av1CompoundType.Wedge)] + [InlineData((int)Av1BitDepth.TwelveBit, (int)Av1CompoundType.Wedge)] + [InlineData((int)Av1BitDepth.EightBit, (int)Av1CompoundType.DifferenceWeighted)] + [InlineData((int)Av1BitDepth.TenBit, (int)Av1CompoundType.DifferenceWeighted)] + [InlineData((int)Av1BitDepth.TwelveBit, (int)Av1CompoundType.DifferenceWeighted)] + public void DecodeBlockReconstructsSelectableCompoundPrediction(int bitDepthValue, int compoundTypeValue) + { + Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue; + Av1CompoundType compoundType = (Av1CompoundType)compoundTypeValue; + ushort firstValue = bitDepth == Av1BitDepth.EightBit ? (ushort)20 : (ushort)100; + ushort secondValue = bitDepth switch + { + Av1BitDepth.EightBit => 41, + Av1BitDepth.TenBit => 701, + _ => 3001, + }; + + ReadOnlySpan wedgeMask = + [ + 0, 0, 0, 1, 1, 2, 4, 6, + 0, 1, 1, 2, 4, 6, 11, 18, + 1, 2, 4, 6, 11, 18, 27, 37, + 4, 6, 11, 18, 27, 37, 46, 53, + 11, 18, 27, 37, 46, 53, 58, 60, + 27, 37, 46, 53, 58, 60, 62, 63, + 46, 53, 58, 60, 62, 63, 63, 64, + 58, 60, 62, 63, 63, 64, 64, 64, + ]; + + ObuSequenceHeader sequenceHeader = CreateSequenceHeader(bitDepth); + sequenceHeader.OrderHintInfo.EnableOrderHint = true; + sequenceHeader.OrderHintInfo.OrderHintBits = 5; + ObuFrameHeader frameHeader = CreateFrameHeader(); + frameHeader.OrderHint = 10; + frameHeader.GetReferenceFrameIndices()[0] = 0; + frameHeader.GetReferenceFrameIndices()[1] = 1; + frameHeader.GetReferenceOrderHints()[0] = 9; + frameHeader.GetReferenceOrderHints()[1] = 5; + + using Av1ReferenceFrameStore referenceFrames = new(); + Assert.True(referenceFrames.Commit(1, CreateReferenceFrame(sequenceHeader, firstValue), showFrame: false)); + Assert.True(referenceFrames.Commit(2, CreateReferenceFrame(sequenceHeader, secondValue), showFrame: false)); + + using Av1FrameBuffer frameBuffer = new( + Configuration.Default, + sequenceHeader, + Av1ColorFormat.Yuv400, + false); + + using Av1FrameInfo frameInfo = new(sequenceHeader); + Av1SuperblockInfo superblockInfo = frameInfo.GetSuperblock(Point.Empty); + superblockInfo.GetTransformInfoY()[0] = new Av1TransformInfo(Av1TransformSize.Size8x8, 0, 0); + + Av1BlockModeInfo modeInfo = new(Av1BlockSize.Block8x8, Point.Empty) + { + Skip = true, + YMode = Av1PredictionMode.NearestNearestMotionVector, + CompoundIndex = compoundType != Av1CompoundType.DistanceWeighted, + CompoundType = compoundType, + CompoundWedgeIndex = 0, + CompoundWedgeSign = true, + DifferenceWeightedMaskType = Av1DifferenceWeightedMaskType.Type38, + }; + + modeInfo.ReferenceFrames[0] = Av1ReferenceFrameType.Last; + modeInfo.ReferenceFrames[1] = Av1ReferenceFrameType.Last2; + modeInfo.InterpolationFilters.Clear(); + modeInfo.SetTransformUnitCount(Av1PlaneType.Y, 1); + + Av1LoopFilterContext loopFilterContext = new(sequenceHeader); + Av1InverseQuantizer inverseQuantizer = new(sequenceHeader, frameHeader); + using Av1BlockDecoder decoder = new( + sequenceHeader, + frameHeader, + frameBuffer, + loopFilterContext, + inverseQuantizer, + referenceFrames); + + decoder.UpdateSuperblock(superblockInfo); + decoder.DecodeBlock( + modeInfo, + Point.Empty, + Av1BlockSize.Block8x8, + superblockInfo, + new Av1TileInfo(0, 0, frameHeader)); + + int differenceShift = bitDepth.GetBitCount() - 8 + 4; + int differenceAlpha = Math.Min(64, 38 + (Math.Abs(firstValue - secondValue) >> differenceShift)); + for (int row = 0; row < 8; row++) + { + for (int column = 0; column < 8; column++) + { + int alpha = compoundType switch + { + Av1CompoundType.Wedge => wedgeMask[(row * 8) + column], + Av1CompoundType.DifferenceWeighted => differenceAlpha, + _ => 52, + }; + + ushort expected = (ushort)(((alpha * firstValue) + ((64 - alpha) * secondValue) + 32) >> 6); + if (bitDepth == Av1BitDepth.EightBit) + { + Span samples = frameBuffer.DeriveBlockPointer(Av1Plane.Y, 0, 0).DangerousGetRowSpan(row); + Assert.Equal((byte)expected, samples[column]); + } + else + { + Span samples = frameBuffer.GetHighBitDepthRowSpan(Av1Plane.Y, row, 0, 0); + Assert.Equal(expected, samples[column]); + } + } + } + } + + /// + /// Verifies smooth inter-intra reconstruction through the production block branch at every supported bit depth. + /// + /// The native sample depth. + [Theory] + [InlineData((int)Av1BitDepth.EightBit)] + [InlineData((int)Av1BitDepth.TenBit)] + [InlineData((int)Av1BitDepth.TwelveBit)] + public void DecodeBlockReconstructsSmoothInterIntraPrediction(int bitDepthValue) + { + Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue; + ushort interValue = bitDepth == Av1BitDepth.EightBit ? (ushort)20 : (ushort)100; + ushort intraValue = (ushort)(1 << (bitDepth.GetBitCount() - 1)); + ushort expected = (ushort)((interValue + intraValue + 1) >> 1); + ObuSequenceHeader sequenceHeader = CreateSequenceHeader(bitDepth); + ObuFrameHeader frameHeader = CreateFrameHeader(); + frameHeader.GetReferenceFrameIndices()[0] = 0; + + using Av1ReferenceFrameStore referenceFrames = new(); + Assert.True(referenceFrames.Commit(1, CreateReferenceFrame(sequenceHeader, interValue), showFrame: false)); + + using Av1FrameBuffer frameBuffer = new( + Configuration.Default, + sequenceHeader, + Av1ColorFormat.Yuv400, + false); + + using Av1FrameInfo frameInfo = new(sequenceHeader); + Av1SuperblockInfo superblockInfo = frameInfo.GetSuperblock(Point.Empty); + superblockInfo.GetTransformInfoY()[0] = new Av1TransformInfo(Av1TransformSize.Size8x8, 0, 0); + + Av1BlockModeInfo modeInfo = new(Av1BlockSize.Block8x8, Point.Empty) + { + Skip = true, + YMode = Av1PredictionMode.NearestMotionVector, + InterIntraMode = Av1InterIntraMode.DC, + UseInterIntraWedge = false, + }; + + modeInfo.ReferenceFrames[0] = Av1ReferenceFrameType.Last; + modeInfo.ReferenceFrames[1] = Av1ReferenceFrameType.Intra; + modeInfo.InterpolationFilters.Clear(); + modeInfo.SetTransformUnitCount(Av1PlaneType.Y, 1); + + Av1LoopFilterContext loopFilterContext = new(sequenceHeader); + Av1InverseQuantizer inverseQuantizer = new(sequenceHeader, frameHeader); + using Av1BlockDecoder decoder = new( + sequenceHeader, + frameHeader, + frameBuffer, + loopFilterContext, + inverseQuantizer, + referenceFrames); + + decoder.UpdateSuperblock(superblockInfo); + decoder.DecodeBlock( + modeInfo, + Point.Empty, + Av1BlockSize.Block8x8, + superblockInfo, + new Av1TileInfo(0, 0, frameHeader)); + + for (int row = 0; row < 8; row++) + { + if (bitDepth == Av1BitDepth.EightBit) + { + Span samples = frameBuffer.DeriveBlockPointer(Av1Plane.Y, 0, 0).DangerousGetRowSpan(row); + for (int column = 0; column < 8; column++) + { + Assert.Equal((byte)expected, samples[column]); + } + } + else + { + Span samples = frameBuffer.GetHighBitDepthRowSpan(Av1Plane.Y, row, 0, 0); + for (int column = 0; column < 8; column++) + { + Assert.Equal(expected, samples[column]); + } + } + } + } + /// /// Creates one independently owned retained frame filled with a constant visible luma value. /// diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundInterPredictorTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundInterPredictorTests.cs index 312d8d6c9c..89a27e1cb1 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundInterPredictorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundInterPredictorTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -32,6 +33,156 @@ public class Av1CompoundInterPredictorTests public void HighBitDepthAverageMatchesIndependentOracleAcrossIntrinsicWidths() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthAverage, PredictorConfigurations); + /// + /// Verifies 8-bit distance and per-sample mask blending across every intrinsic width and scalar tail. + /// + [Fact] + public void ByteSelectableBlendsMatchIndependentOracleAcrossIntrinsicWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateByteSelectableBlends, PredictorConfigurations); + + /// + /// Verifies 10/12-bit distance and per-sample mask blending across every intrinsic width and scalar tail. + /// + [Fact] + public void HighBitDepthSelectableBlendsMatchIndependentOracleAcrossIntrinsicWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthSelectableBlends, PredictorConfigurations); + + /// + /// Verifies the four smooth inter-intra modes and their complemented destination orientation. + /// + [Fact] + public void SmoothInterIntraMasksMatchPinnedWeights() + { + ReadOnlySpan weights = [60, 34, 19, 11, 6, 4, 2, 1]; + + foreach (Av1InterIntraMode mode in Enum.GetValues()) + { + const int width = 8; + const int height = 4; + const int stride = 11; + byte[] mask = new byte[stride * height]; + byte[] inverted = new byte[stride * height]; + mask.AsSpan().Fill(0xA5); + inverted.AsSpan().Fill(0xA5); + + Av1CompoundInterPredictor.FillInterIntraMask(mask, stride, width, height, mode, invert: false); + Av1CompoundInterPredictor.FillInterIntraMask(inverted, stride, width, height, mode, invert: true); + + for (int row = 0; row < height; row++) + { + for (int column = 0; column < width; column++) + { + byte expected = mode switch + { + Av1InterIntraMode.Vertical => weights[row], + Av1InterIntraMode.Horizontal => weights[column], + Av1InterIntraMode.Smooth => weights[Math.Min(row, column)], + _ => 32, + }; + + Assert.Equal(expected, mask[(row * stride) + column]); + Assert.Equal((byte)(64 - expected), inverted[(row * stride) + column]); + } + + for (int column = width; column < stride; column++) + { + Assert.Equal(0xA5, mask[(row * stride) + column]); + Assert.Equal(0xA5, inverted[(row * stride) + column]); + } + } + } + } + + /// + /// Verifies both difference-mask orientations at each supported bit depth. + /// + [Fact] + public void DifferenceWeightedMasksMatchPinnedFormula() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateDifferenceWeightedMasks, PredictorConfigurations); + + /// + /// Applies the pinned difference-mask formula at every bit depth and intrinsic width. + /// + private static void ValidateDifferenceWeightedMasks() + { + ReadOnlySpan widths = [9, 16, 23, 32, 47, 64, 127]; + + foreach (int width in widths) + { + const int height = 3; + int firstStride = width + 4; + int secondStride = width + 2; + int maskStride = width + 3; + foreach (int bitDepth in new[] { 8, 10, 12 }) + { + int sampleMask = (1 << bitDepth) - 1; + ushort[] first = new ushort[firstStride * height]; + ushort[] second = new ushort[secondStride * height]; + for (int row = 0; row < height; row++) + { + for (int column = 0; column < width; column++) + { + first[(row * firstStride) + column] = (ushort)(((row * 911) + (column * 521)) & sampleMask); + second[(row * secondStride) + column] = (ushort)(((row * 307) + (column * 997) + 31) & sampleMask); + } + } + + foreach (Av1DifferenceWeightedMaskType maskType in Enum.GetValues()) + { + byte[] actual = new byte[maskStride * height]; + actual.AsSpan().Fill(0xA5); + + if (bitDepth == 8) + { + byte[] firstByte = Array.ConvertAll(first, value => (byte)value); + byte[] secondByte = Array.ConvertAll(second, value => (byte)value); + Av1CompoundInterPredictor.FillDifferenceWeightedMask( + actual, + maskStride, + firstByte, + firstStride, + secondByte, + secondStride, + width, + height, + maskType); + } + else + { + Av1CompoundInterPredictor.FillDifferenceWeightedMask( + actual, + maskStride, + first, + firstStride, + second, + secondStride, + width, + height, + bitDepth, + maskType); + } + + for (int row = 0; row < height; row++) + { + for (int column = 0; column < width; column++) + { + int difference = Math.Abs(first[(row * firstStride) + column] - second[(row * secondStride) + column]); + int alpha = Math.Min(64, 38 + ((difference >> (bitDepth - 8)) / 16)); + byte expected = (byte)(maskType == Av1DifferenceWeightedMaskType.Type38Inverse ? 64 - alpha : alpha); + + Assert.Equal(expected, actual[(row * maskStride) + column]); + } + + for (int column = width; column < maskStride; column++) + { + Assert.Equal(0xA5, actual[(row * maskStride) + column]); + } + } + } + } + } + } + /// /// Applies independent byte arithmetic to block widths that cross every vector and scalar boundary. /// @@ -113,6 +264,169 @@ public class Av1CompoundInterPredictorTests } } + /// + /// Applies independent byte arithmetic to every selectable compound blend. + /// + private static void ValidateByteSelectableBlends() + { + ReadOnlySpan widths = [4, 7, 8, 15, 16, 23, 31, 32, 47, 64, 127, 128]; + ReadOnlySpan distanceWeights = [9, 7, 11, 5, 12, 4, 13, 3]; + + foreach (int width in widths) + { + const int height = 5; + int destinationStride = width + 11; + int secondStride = width + 7; + int maskStride = width + 5; + byte[] first = new byte[destinationStride * height]; + byte[] second = new byte[secondStride * height]; + byte[] mask = new byte[maskStride * height]; + + FillByteInputs(first, second, destinationStride, secondStride, width, height); + FillMask(mask, maskStride, width, height); + + for (int weightIndex = 0; weightIndex < distanceWeights.Length; weightIndex += 2) + { + byte[] expected = (byte[])first.Clone(); + byte[] actual = (byte[])first.Clone(); + int firstWeight = distanceWeights[weightIndex]; + int secondWeight = distanceWeights[weightIndex + 1]; + + for (int row = 0; row < height; row++) + { + for (int column = 0; column < width; column++) + { + int destinationIndex = (row * destinationStride) + column; + int secondIndex = (row * secondStride) + column; + expected[destinationIndex] = (byte)(((expected[destinationIndex] * firstWeight) + + (second[secondIndex] * secondWeight) + 8) >> 4); + } + } + + Av1CompoundInterPredictor.DistanceWeighted( + actual, + destinationStride, + second, + secondStride, + width, + height, + firstWeight, + secondWeight); + + Assert.Equal(expected, actual); + } + + byte[] maskedExpected = (byte[])first.Clone(); + byte[] maskedActual = (byte[])first.Clone(); + for (int row = 0; row < height; row++) + { + for (int column = 0; column < width; column++) + { + int destinationIndex = (row * destinationStride) + column; + int secondIndex = (row * secondStride) + column; + int alpha = mask[(row * maskStride) + column]; + maskedExpected[destinationIndex] = (byte)(((alpha * maskedExpected[destinationIndex]) + + ((64 - alpha) * second[secondIndex]) + 32) >> 6); + } + } + + Av1CompoundInterPredictor.Blend( + maskedActual, + destinationStride, + second, + secondStride, + mask, + maskStride, + width, + height); + + Assert.Equal(maskedExpected, maskedActual); + } + } + + /// + /// Applies independent high-bit-depth arithmetic to every selectable compound blend. + /// + private static void ValidateHighBitDepthSelectableBlends() + { + ReadOnlySpan widths = [4, 7, 8, 15, 16, 23, 31, 32, 47, 64, 127, 128]; + ReadOnlySpan distanceWeights = [9, 7, 11, 5, 12, 4, 13, 3]; + + foreach (int bitDepth in new[] { 10, 12 }) + { + foreach (int width in widths) + { + const int height = 5; + int destinationStride = width + 9; + int secondStride = width + 5; + int maskStride = width + 3; + ushort[] first = new ushort[destinationStride * height]; + ushort[] second = new ushort[secondStride * height]; + byte[] mask = new byte[maskStride * height]; + + FillHighBitDepthInputs(first, second, destinationStride, secondStride, width, height, bitDepth); + FillMask(mask, maskStride, width, height); + + for (int weightIndex = 0; weightIndex < distanceWeights.Length; weightIndex += 2) + { + ushort[] expected = (ushort[])first.Clone(); + ushort[] actual = (ushort[])first.Clone(); + int firstWeight = distanceWeights[weightIndex]; + int secondWeight = distanceWeights[weightIndex + 1]; + + for (int row = 0; row < height; row++) + { + for (int column = 0; column < width; column++) + { + int destinationIndex = (row * destinationStride) + column; + int secondIndex = (row * secondStride) + column; + expected[destinationIndex] = (ushort)(((expected[destinationIndex] * firstWeight) + + (second[secondIndex] * secondWeight) + 8) >> 4); + } + } + + Av1CompoundInterPredictor.DistanceWeighted( + actual, + destinationStride, + second, + secondStride, + width, + height, + firstWeight, + secondWeight); + + Assert.Equal(expected, actual); + } + + ushort[] maskedExpected = (ushort[])first.Clone(); + ushort[] maskedActual = (ushort[])first.Clone(); + for (int row = 0; row < height; row++) + { + for (int column = 0; column < width; column++) + { + int destinationIndex = (row * destinationStride) + column; + int secondIndex = (row * secondStride) + column; + int alpha = mask[(row * maskStride) + column]; + maskedExpected[destinationIndex] = (ushort)(((alpha * maskedExpected[destinationIndex]) + + ((64 - alpha) * second[secondIndex]) + 32) >> 6); + } + } + + Av1CompoundInterPredictor.Blend( + maskedActual, + destinationStride, + second, + secondStride, + mask, + maskStride, + width, + height); + + Assert.Equal(maskedExpected, maskedActual); + } + } + } + /// /// Fills active byte samples while assigning different sentinels to the unused row tails. /// @@ -162,4 +476,19 @@ public class Av1CompoundInterPredictorTests } } } + + /// + /// Fills active mask samples across the complete AV1 alpha range while guarding every row tail. + /// + private static void FillMask(Span mask, int maskStride, int width, int height) + { + mask.Fill(0xA5); + for (int row = 0; row < height; row++) + { + for (int column = 0; column < width; column++) + { + mask[(row * maskStride) + column] = (byte)(((row * 19) + (column * 37)) % 65); + } + } + } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterFrameModeInfoTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterFrameModeInfoTests.cs index c447a2ab3e..e4eca6f778 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterFrameModeInfoTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterFrameModeInfoTests.cs @@ -223,6 +223,87 @@ public class Av1InterFrameModeInfoTests Assert.Equal(Av1CompoundType.Average, modeInfo.CompoundType); } + /// + /// Verifies selectable compound syntax in its normative position before interpolation filtering. + /// + /// The selected compound operation. + [Theory] + [InlineData((int)Av1CompoundType.DistanceWeighted)] + [InlineData((int)Av1CompoundType.Wedge)] + [InlineData((int)Av1CompoundType.DifferenceWeighted)] + public void ReadInterFrameModeInfoReadsSelectableCompoundBeforeInterpolation(int compoundTypeValue) + { + Av1CompoundType compoundType = (Av1CompoundType)compoundTypeValue; + ObuSequenceHeader sequenceHeader = CreateSequenceHeader(); + sequenceHeader.EnableMaskedCompound = true; + sequenceHeader.EnableDualFilter = false; + sequenceHeader.OrderHintInfo.EnableOrderHint = true; + sequenceHeader.OrderHintInfo.EnableJointCompound = true; + sequenceHeader.OrderHintInfo.OrderHintBits = 3; + ObuFrameHeader frameHeader = CreateFrameHeader(); + frameHeader.ReferenceMode = ObuReferenceMode.ReferenceModeSelect; + frameHeader.InterpolationFilter = Av1InterpolationFilter.Switchable; + frameHeader.OrderHint = 4; + frameHeader.GetReferenceFrameIndices()[0] = 0; + frameHeader.GetReferenceFrameIndices()[1] = 1; + frameHeader.GetReferenceOrderHints()[0] = 3; + frameHeader.GetReferenceOrderHints()[1] = 5; + + using Av1TileReader tileReader = new(Configuration.Default, sequenceHeader, frameHeader); + Av1BlockModeInfo modeInfo = new(Av1BlockSize.Block8x8, Point.Empty); + using Av1SymbolWriter writer = new(Configuration.Default, 12, updateCdf: true); + writer.WriteSymbol(false, Av1DefaultDistributions.Skip[0]); + writer.WriteSymbol(true, Av1DefaultDistributions.IntraInter[0]); + writer.WriteSymbol(true, Av1DefaultDistributions.CompInter[1]); + WriteCompoundReferencePair(writer, pairIndex: 1); + writer.WriteSymbol(0, Av1DefaultDistributions.InterCompoundMode[0]); + + bool masked = compoundType is Av1CompoundType.Wedge or Av1CompoundType.DifferenceWeighted; + writer.WriteSymbol(masked, Av1DefaultDistributions.CompoundGroupIndex[0]); + if (masked) + { + writer.WriteSymbol( + compoundType == Av1CompoundType.Wedge ? 0 : 1, + Av1DefaultDistributions.CompoundType[(int)Av1BlockSize.Block8x8]); + + if (compoundType == Av1CompoundType.Wedge) + { + writer.WriteSymbol(13, Av1DefaultDistributions.WedgeIndex[(int)Av1BlockSize.Block8x8]); + writer.WriteLiteral(true); + } + else + { + writer.WriteLiteral(true); + } + } + else + { + // Equal reference distances select context three; false chooses distance weighting. + writer.WriteSymbol(false, Av1DefaultDistributions.CompoundIndex[3]); + } + + writer.WriteSymbol((int)Av1InterpolationFilter.Sharp, Av1DefaultDistributions.SwitchableInterpolation[3]); + + using IMemoryOwner encoded = writer.Exit(); + ReadInterFrameModeInfo(tileReader, encoded.Memory, modeInfo); + + Assert.Equal(Av1ReferenceFrameType.Last, modeInfo.ReferenceFrames[0]); + Assert.Equal(Av1ReferenceFrameType.Last2, modeInfo.ReferenceFrames[1]); + Assert.Equal(masked, modeInfo.CompoundGroupIndex); + Assert.Equal(compoundType != Av1CompoundType.DistanceWeighted, modeInfo.CompoundIndex); + Assert.Equal(compoundType, modeInfo.CompoundType); + Assert.Equal(compoundType == Av1CompoundType.Wedge ? 13 : 0, modeInfo.CompoundWedgeIndex); + Assert.Equal(compoundType == Av1CompoundType.Wedge, modeInfo.CompoundWedgeSign); + Assert.Equal( + compoundType == Av1CompoundType.DifferenceWeighted + ? Av1DifferenceWeightedMaskType.Type38Inverse + : Av1DifferenceWeightedMaskType.Type38, + modeInfo.DifferenceWeightedMaskType); + + Assert.Equal(Av1InterpolationFilter.Sharp, modeInfo.InterpolationFilters[0]); + Assert.Equal(Av1InterpolationFilter.Sharp, modeInfo.InterpolationFilters[1]); + } + /// /// Invokes the ref-struct mode parser with one available above neighbor. /// diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeInfoTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeInfoTests.cs index 1f183d31c4..dd818ecd2d 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeInfoTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeInfoTests.cs @@ -141,6 +141,55 @@ public class Av1MotionModeInfoTests Assert.Equal(Av1InterpolationFilter.Sharp, modeInfo.InterpolationFilters[1]); } + /// + /// Verifies selected smooth and wedge inter-intra syntax before switchable interpolation. + /// + /// Whether the block selects an inter-intra wedge. + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ReadInterFrameModeInfoReadsSelectedInterIntraBeforeInterpolation(bool useWedge) + { + ObuSequenceHeader sequenceHeader = CreateSequenceHeader(); + sequenceHeader.EnableInterIntraCompound = true; + ObuFrameHeader frameHeader = CreateFrameHeader(); + ConfigureForcedTranslationalGlobalMotion(frameHeader); + + using Av1TileReader tileReader = new(Configuration.Default, sequenceHeader, frameHeader); + Av1BlockModeInfo modeInfo = new(Av1BlockSize.Block8x8, Point.Empty); + Av1SuperblockInfo superblockInfo = new(tileReader.FrameInfo, Point.Empty); + Av1PartitionInfo partitionInfo = new(modeInfo, superblockInfo, false, Av1PartitionType.None); + + using Av1SymbolWriter writer = new(Configuration.Default, 6, updateCdf: true); + writer.WriteSymbol(false, Av1DefaultDistributions.Skip[0]); + writer.WriteSymbol(true, Av1DefaultDistributions.InterIntra[Av1BlockSize.Block8x8.GetSizeGroup()]); + writer.WriteSymbol( + (int)Av1InterIntraMode.Smooth, + Av1DefaultDistributions.InterIntraMode[Av1BlockSize.Block8x8.GetSizeGroup()]); + + writer.WriteSymbol(useWedge, Av1DefaultDistributions.WedgeInterIntra[(int)Av1BlockSize.Block8x8]); + if (useWedge) + { + writer.WriteSymbol(13, Av1DefaultDistributions.WedgeIndex[(int)Av1BlockSize.Block8x8]); + } + + writer.WriteSymbol((int)Av1InterpolationFilter.Sharp, Av1DefaultDistributions.SwitchableInterpolation[3]); + + using IMemoryOwner encoded = writer.Exit(); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.Memory.Span, 0, updateCdf: true); + + tileReader.ReadInterFrameModeInfo(ref decoder, ref partitionInfo, new Av1TileInfo(0, 0, frameHeader)); + + Assert.Equal(Av1ReferenceFrameType.Last, modeInfo.ReferenceFrames[0]); + Assert.Equal(Av1ReferenceFrameType.Intra, modeInfo.ReferenceFrames[1]); + Assert.Equal(Av1InterIntraMode.Smooth, modeInfo.InterIntraMode); + Assert.Equal(useWedge, modeInfo.UseInterIntraWedge); + Assert.Equal(useWedge ? 13 : 0, modeInfo.InterIntraWedgeIndex); + Assert.Equal(Av1MotionMode.SimpleTranslation, modeInfo.MotionMode); + Assert.Equal(Av1InterpolationFilter.Sharp, modeInfo.InterpolationFilters[0]); + Assert.Equal(Av1InterpolationFilter.Sharp, modeInfo.InterpolationFilters[1]); + } + /// /// Creates the monochrome 64x64 sequence geometry used by direct inter-mode syntax tests. /// diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs index 896e1ebf6e..a05446ae37 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs @@ -138,6 +138,41 @@ public class Av1ReconstructionConformanceTests /// private const int AverageCompoundFixtureFrameCount = 19; + /// + /// The coverage bit representing distance-weighted compound prediction. + /// + private const int DistanceWeightedCompoundCoverage = 1 << 0; + + /// + /// The coverage bit representing a non-inverted wedge compound mask. + /// + private const int WedgeCompoundCoverage = 1 << 1; + + /// + /// The coverage bit representing an inverted wedge compound mask. + /// + private const int InvertedWedgeCompoundCoverage = 1 << 2; + + /// + /// The coverage bit representing the first difference-weighted mask orientation. + /// + private const int DifferenceWeightedCompoundCoverage = 1 << 3; + + /// + /// The coverage bit representing the inverted difference-weighted mask orientation. + /// + private const int InvertedDifferenceWeightedCompoundCoverage = 1 << 4; + + /// + /// The coverage bit representing smooth inter-intra prediction. + /// + private const int SmoothInterIntraCoverage = 1 << 5; + + /// + /// The coverage bit representing wedge inter-intra prediction. + /// + private const int WedgeInterIntraCoverage = 1 << 6; + /// /// The hardware configurations covering the available vector widths and the scalar color-conversion fallback. /// @@ -599,6 +634,263 @@ public class Av1ReconstructionConformanceTests Assert.Equal(presentationReference is not null, presentationCompared); } + /// + /// Verifies every selectable compound and inter-intra production branch against pinned native and presentation references. + /// + [Fact] + public void DecodeRealLibavifSequencesWithSelectableCompoundAndInterIntraMatchesPinnedReferences() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + ValidateSelectableCompoundSequencesWithDefaultConfiguration, + ReconstructionConfigurations); + + /// + /// Verifies selectable compound and inter-intra reconstruction through a constrained allocator. + /// + [Fact] + [ValidateDisposedMemoryAllocations] + public void DecodeRealLibavifSequencesWithSelectableCompoundAndInterIntraUseContiguousPlanes() + { + ValidateSelectableCompoundSequenceWithConstrainedAllocator( + TestImages.Heif.Av1DistanceWeightedCompoundSequenceAvif, + TestImages.Heif.Av1DistanceWeightedCompoundSequenceNativeReference, + TestImages.Heif.Av1DistanceWeightedCompoundSequencePresentationReference, + DistanceWeightedCompoundCoverage); + + ValidateSelectableCompoundSequenceWithConstrainedAllocator( + TestImages.Heif.Av1WedgeCompoundSequenceAvif, + TestImages.Heif.Av1WedgeCompoundSequenceNativeReference, + TestImages.Heif.Av1WedgeCompoundSequencePresentationReference, + WedgeCompoundCoverage | InvertedWedgeCompoundCoverage); + + ValidateSelectableCompoundSequenceWithConstrainedAllocator( + TestImages.Heif.Av1DifferenceWeightedCompoundSequenceAvif, + TestImages.Heif.Av1DifferenceWeightedCompoundSequenceNativeReference, + TestImages.Heif.Av1DifferenceWeightedCompoundSequencePresentationReference, + DifferenceWeightedCompoundCoverage | InvertedDifferenceWeightedCompoundCoverage); + + ValidateSelectableCompoundSequenceWithConstrainedAllocator( + TestImages.Heif.Av1InterIntraSequenceAvif, + TestImages.Heif.Av1InterIntraSequenceNativeReference, + TestImages.Heif.Av1InterIntraSequencePresentationReference, + SmoothInterIntraCoverage | WedgeInterIntraCoverage); + } + + /// + /// Verifies one complete selectable-compound sequence with a separately tracked constrained allocator. + /// + private static void ValidateSelectableCompoundSequenceWithConstrainedAllocator( + string imagePath, + string nativeReferencePath, + string presentationReferencePath, + int requiredCoverage) + { + TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 1_024 }; + allocator.EnableNonThreadSafeLogging(); + Configuration configuration = Configuration.Default.Clone(); + configuration.MemoryAllocator = allocator; + + ValidateSelectableCompoundSequence( + configuration, + imagePath, + nativeReferencePath, + presentationReferencePath, + requiredCoverage, + comparePresentation: false); + + Assert.Contains(allocator.AllocationLog, request => request.ElementType.Name == "RetainedMotionFieldEntry"); + Assert.Contains(allocator.AllocationLog, request => request.ElementType.Name == "TemporalMotionFieldEntry"); + Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); + Assert.All( + allocator.AllocationLog, + allocation => Assert.Single( + allocator.ReturnLog, + returned => returned.HashCodeOfBuffer == allocation.HashCodeOfBuffer)); + } + + /// + /// Runs every selectable compound fixture with exact final presentation comparison. + /// + private static void ValidateSelectableCompoundSequencesWithDefaultConfiguration() + => ValidateSelectableCompoundSequences(Configuration.Default, comparePresentation: true); + + /// + /// Validates every selectable compound fixture with the requested decoder configuration. + /// + /// The decoder configuration. + /// Whether to compare the final presented frame. + private static void ValidateSelectableCompoundSequences(Configuration configuration, bool comparePresentation) + { + ValidateSelectableCompoundSequence( + configuration, + TestImages.Heif.Av1DistanceWeightedCompoundSequenceAvif, + TestImages.Heif.Av1DistanceWeightedCompoundSequenceNativeReference, + TestImages.Heif.Av1DistanceWeightedCompoundSequencePresentationReference, + DistanceWeightedCompoundCoverage, + comparePresentation); + + ValidateSelectableCompoundSequence( + configuration, + TestImages.Heif.Av1WedgeCompoundSequenceAvif, + TestImages.Heif.Av1WedgeCompoundSequenceNativeReference, + TestImages.Heif.Av1WedgeCompoundSequencePresentationReference, + WedgeCompoundCoverage | InvertedWedgeCompoundCoverage, + comparePresentation); + + ValidateSelectableCompoundSequence( + configuration, + TestImages.Heif.Av1DifferenceWeightedCompoundSequenceAvif, + TestImages.Heif.Av1DifferenceWeightedCompoundSequenceNativeReference, + TestImages.Heif.Av1DifferenceWeightedCompoundSequencePresentationReference, + DifferenceWeightedCompoundCoverage | InvertedDifferenceWeightedCompoundCoverage, + comparePresentation); + + ValidateSelectableCompoundSequence( + configuration, + TestImages.Heif.Av1InterIntraSequenceAvif, + TestImages.Heif.Av1InterIntraSequenceNativeReference, + TestImages.Heif.Av1InterIntraSequencePresentationReference, + SmoothInterIntraCoverage | WedgeInterIntraCoverage, + comparePresentation); + } + + /// + /// Decodes one complete retained-reference sequence and compares its final native and presented samples exactly. + /// + private static void ValidateSelectableCompoundSequence( + Configuration configuration, + string imagePath, + string nativeReferencePath, + string presentationReferencePath, + int requiredCoverage, + bool comparePresentation) + { + byte[] fileBytes = TestFile.Create(imagePath).Bytes; + byte[] referenceBytes = TestFile.Create(nativeReferencePath).Bytes; + ReadOnlySpan fileHeader = + "YUV4MPEG2 W80 H80 F25:1 Ip A0:0 C444 XYSCSS=444 XCOLORRANGE=LIMITED\n"u8; + + ReadOnlySpan frameHeader = "FRAME\n"u8; + + ReadOnlySpan nativeReference = referenceBytes; + Assert.True(nativeReference.StartsWith(fileHeader)); + nativeReference = nativeReference[fileHeader.Length..]; + Assert.True(nativeReference.StartsWith(frameHeader)); + nativeReference = nativeReference[frameHeader.Length..]; + Assert.Equal(AverageCompoundFixtureSize * AverageCompoundFixtureSize * 3, nativeReference.Length); + + using Image presentationReference = + Image.Load(TestFile.Create(presentationReferencePath).Bytes); + + HeifSequence sequence = ParseImageSequence(fileBytes); + HeifSequenceTrack track = sequence.ColorTrack; + int coverage = 0; + int visibleFrameCount = 0; + bool nativeCompared = false; + bool presentationCompared = false; + + using Av1Decoder decoder = new(configuration); + for (int sampleIndex = 0; sampleIndex < track.Samples.Length; sampleIndex++) + { + HeifSequenceSample sample = track.Samples[sampleIndex]; + Span sampleData = fileBytes.AsSpan((int)sample.Offset, sample.Length); + if (sample.IsHidden) + { + decoder.DecodeSequenceReference( + sampleData, + track.CicpProfile, + track.Av1CodecConfiguration); + + coverage |= GetSelectableCompoundCoverage(decoder); + continue; + } + + using ImageFrame frame = decoder.DecodeSequenceFrame( + sampleData, + track.CicpProfile, + track.Av1CodecConfiguration); + + Av1FrameBuffer frameBuffer = Assert.IsType>(decoder.FrameBuffer); + coverage |= GetSelectableCompoundCoverage(decoder); + + // Every compound branch retains the same row-addressed plane contract under constrained allocators. + Assert.Equal(1, frameBuffer.BufferY!.FastMemoryGroup.Count); + Assert.Equal(1, frameBuffer.BufferCb!.FastMemoryGroup.Count); + Assert.Equal(1, frameBuffer.BufferCr!.FastMemoryGroup.Count); + + if (visibleFrameCount == AverageCompoundFixtureFrameCount - 1) + { + AssertNativePlanesEqual(decoder, frameBuffer, nativeReference); + nativeCompared = true; + + if (comparePresentation) + { + ImageSimilarityReport report = + ImageComparer.Exact.CompareImagesOrFrames( + visibleFrameCount, + presentationReference.Frames.RootFrame, + frame); + + Assert.True(report.IsEmpty, report.ToString()); + presentationCompared = true; + } + } + + visibleFrameCount++; + } + + Assert.Equal(AverageCompoundFixtureFrameCount, visibleFrameCount); + Assert.Equal(requiredCoverage, coverage & requiredCoverage); + Assert.True(nativeCompared); + Assert.Equal(comparePresentation, presentationCompared); + } + + /// + /// Collects the selectable compound and inter-intra modes retained in one decoded frame. + /// + private static int GetSelectableCompoundCoverage(Av1Decoder decoder) + { + ObuSequenceHeader sequenceHeader = Assert.IsType(decoder.SequenceHeader); + Av1FrameInfo frameInfo = Assert.IsType(decoder.FrameInfo); + int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2; + int superblockColumnCount = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, superblockSizeLog2) >> superblockSizeLog2; + int superblockRowCount = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameHeight, superblockSizeLog2) >> superblockSizeLog2; + int coverage = 0; + for (int superblockRow = 0; superblockRow < superblockRowCount; superblockRow++) + { + for (int superblockColumn = 0; superblockColumn < superblockColumnCount; superblockColumn++) + { + Av1SuperblockInfo superblockInfo = frameInfo.GetSuperblock(new Point(superblockColumn, superblockRow)); + foreach (Av1BlockModeInfo modeInfo in superblockInfo.GetModeInfos()) + { + if (modeInfo.ReferenceFrames[1] == Av1ReferenceFrameType.Intra) + { + coverage |= modeInfo.UseInterIntraWedge ? WedgeInterIntraCoverage : SmoothInterIntraCoverage; + continue; + } + + if (modeInfo.ReferenceFrames[1] <= Av1ReferenceFrameType.Intra) + { + continue; + } + + coverage |= modeInfo.CompoundType switch + { + Av1CompoundType.DistanceWeighted => DistanceWeightedCompoundCoverage, + Av1CompoundType.Wedge => modeInfo.CompoundWedgeSign + ? InvertedWedgeCompoundCoverage + : WedgeCompoundCoverage, + Av1CompoundType.DifferenceWeighted => modeInfo.DifferenceWeightedMaskType == Av1DifferenceWeightedMaskType.Type38Inverse + ? InvertedDifferenceWeightedCompoundCoverage + : DifferenceWeightedCompoundCoverage, + _ => 0, + }; + } + } + } + + return coverage; + } + /// /// Verifies lossless syntax, residual reconstruction, and exact native samples against scalar libaom for /// independently encoded eight-, ten-, and twelve-bit AVIF images. diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SelectableCompoundEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SelectableCompoundEntropyTests.cs new file mode 100644 index 0000000000..fe91084428 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SelectableCompoundEntropyTests.cs @@ -0,0 +1,154 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +/// +/// Verifies the adaptive syntax distributions used by selectable compound and inter-intra prediction. +/// +[Trait("Format", "Avif")] +public class Av1SelectableCompoundEntropyTests +{ + /// + /// Verifies representative and complete multi-symbol defaults against pinned libaom's forward Q15 tables. + /// + [Fact] + public void DefaultsMatchPinnedLibaom() + { + AssertForwardThresholds(Av1DefaultDistributions.InterIntraMode[1], [1875, 11082, 27332]); + AssertForwardThresholds(Av1DefaultDistributions.WedgeInterIntra[(int)Av1BlockSize.Block8x8], [20036]); + AssertForwardThresholds(Av1DefaultDistributions.CompoundType[(int)Av1BlockSize.Block8x8], [23431]); + AssertForwardThresholds( + Av1DefaultDistributions.WedgeIndex[(int)Av1BlockSize.Block8x8], + [2438, 4440, 6599, 8663, 11005, 12874, 15751, 18094, 20359, 22362, 24127, 25702, 27752, 29450, 31171]); + + ReadOnlySpan compoundIndex = [18244, 12865, 7053, 13259, 9334, 4644]; + ReadOnlySpan compoundGroupIndex = [26607, 22891, 18840, 24594, 19934, 22674]; + + for (int context = 0; context < compoundIndex.Length; context++) + { + AssertForwardThresholds(Av1DefaultDistributions.CompoundIndex[context], [compoundIndex[context]]); + AssertForwardThresholds(Av1DefaultDistributions.CompoundGroupIndex[context], [compoundGroupIndex[context]]); + } + } + + /// + /// Verifies that every new reader selects and adapts its intended context without consuming adjacent syntax. + /// + [Fact] + public void ReadersRoundTripInNormativeOrder() + { + const int groupContext = 4; + const int compoundIndexContext = 2; + Av1BlockSize blockSize = Av1BlockSize.Block8x8; + using Av1SymbolWriter writer = new(Configuration.Default, 32, updateCdf: true); + writer.WriteSymbol((int)Av1InterIntraMode.Horizontal, Av1DefaultDistributions.InterIntraMode[blockSize.GetSizeGroup()]); + writer.WriteSymbol(true, Av1DefaultDistributions.WedgeInterIntra[(int)blockSize]); + writer.WriteSymbol(13, Av1DefaultDistributions.WedgeIndex[(int)blockSize]); + writer.WriteSymbol(true, Av1DefaultDistributions.CompoundGroupIndex[groupContext]); + writer.WriteSymbol(false, Av1DefaultDistributions.CompoundIndex[compoundIndexContext]); + writer.WriteSymbol(1, Av1DefaultDistributions.CompoundType[(int)blockSize]); + + using IMemoryOwner encoded = writer.Exit(); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0, updateCdf: true); + + Assert.Equal(Av1InterIntraMode.Horizontal, decoder.ReadInterIntraMode(blockSize)); + Assert.True(decoder.ReadUseInterIntraWedge(blockSize)); + Assert.Equal(13, decoder.ReadWedgeIndex(blockSize)); + Assert.True(decoder.ReadCompoundGroupIndex(groupContext)); + Assert.False(decoder.ReadCompoundIndex(compoundIndexContext)); + Assert.Equal(Av1CompoundType.DifferenceWeighted, decoder.ReadMaskedCompoundType(blockSize)); + } + + /// + /// Verifies copying, resetting, and snapshot publication for every selectable-compound distribution family. + /// + [Fact] + public void FrameEntropyLifecycleIncludesSelectableCompoundFamilies() + { + Av1FrameEntropyContext source = new(0); + Av1FrameEntropyContext copy = new(0); + Av1FrameEntropyContext snapshot = new(0); + Av1FrameEntropyContext defaults = new(0); + Av1Distribution[] sourceDistributions = + [ + source.InterIntraMode[1], + source.WedgeInterIntra[(int)Av1BlockSize.Block8x8], + source.CompoundType[(int)Av1BlockSize.Block8x8], + source.WedgeIndex[(int)Av1BlockSize.Block8x8], + source.CompoundIndex[2], + source.CompoundGroupIndex[4], + ]; + + Av1Distribution[] copyDistributions = + [ + copy.InterIntraMode[1], + copy.WedgeInterIntra[(int)Av1BlockSize.Block8x8], + copy.CompoundType[(int)Av1BlockSize.Block8x8], + copy.WedgeIndex[(int)Av1BlockSize.Block8x8], + copy.CompoundIndex[2], + copy.CompoundGroupIndex[4], + ]; + + Av1Distribution[] snapshotDistributions = + [ + snapshot.InterIntraMode[1], + snapshot.WedgeInterIntra[(int)Av1BlockSize.Block8x8], + snapshot.CompoundType[(int)Av1BlockSize.Block8x8], + snapshot.WedgeIndex[(int)Av1BlockSize.Block8x8], + snapshot.CompoundIndex[2], + snapshot.CompoundGroupIndex[4], + ]; + + Av1Distribution[] defaultDistributions = + [ + defaults.InterIntraMode[1], + defaults.WedgeInterIntra[(int)Av1BlockSize.Block8x8], + defaults.CompoundType[(int)Av1BlockSize.Block8x8], + defaults.WedgeIndex[(int)Av1BlockSize.Block8x8], + defaults.CompoundIndex[2], + defaults.CompoundGroupIndex[4], + ]; + + foreach (Av1Distribution distribution in sourceDistributions) + { + distribution.Update(distribution.NumberOfSymbols - 1); + } + + copy.CopyFrom(source); + source.SnapshotTo(snapshot); + + for (int index = 0; index < sourceDistributions.Length; index++) + { + Assert.NotSame(sourceDistributions[index], copyDistributions[index]); + Assert.NotSame(sourceDistributions[index], snapshotDistributions[index]); + Assert.Equal(sourceDistributions[index][0], copyDistributions[index][0]); + Assert.Equal(sourceDistributions[index][0], snapshotDistributions[index][0]); + } + + source.ResetToDefaults(0); + + for (int index = 0; index < sourceDistributions.Length; index++) + { + Assert.Equal(defaultDistributions[index][0], sourceDistributions[index][0]); + } + } + + /// + /// Compares one inverse-cumulative distribution with pinned forward thresholds. + /// + private static void AssertForwardThresholds(Av1Distribution distribution, ReadOnlySpan forwardThresholds) + { + Assert.Equal(forwardThresholds.Length + 1, distribution.NumberOfSymbols); + for (int index = 0; index < forwardThresholds.Length; index++) + { + Assert.Equal((uint)Av1Distribution.ProbabilityTop - forwardThresholds[index], distribution[index]); + } + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 1995ead7e7..e4c24f8049 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1360,6 +1360,18 @@ public static class TestImages public const string Av1AverageCompoundSequenceAvif = "Heif/Av1/Conformance/libavif-webp-logo-average-compound.avif"; public const string Av1AverageCompoundSequenceNativeReference = "Heif/Av1/Conformance/libavif-webp-logo-average-compound-libaom.y4m"; public const string Av1AverageCompoundSequencePresentationReference = "Heif/Av1/Conformance/libavif-webp-logo-average-compound-libavif.png"; + public const string Av1DistanceWeightedCompoundSequenceAvif = "Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound.avif"; + public const string Av1DistanceWeightedCompoundSequenceNativeReference = "Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound-libaom.y4m"; + public const string Av1DistanceWeightedCompoundSequencePresentationReference = "Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound-libavif.png"; + public const string Av1WedgeCompoundSequenceAvif = "Heif/Av1/Conformance/libavif-webp-logo-wedge-compound.avif"; + public const string Av1WedgeCompoundSequenceNativeReference = "Heif/Av1/Conformance/libavif-webp-logo-wedge-compound-libaom.y4m"; + public const string Av1WedgeCompoundSequencePresentationReference = "Heif/Av1/Conformance/libavif-webp-logo-wedge-compound-libavif.png"; + public const string Av1DifferenceWeightedCompoundSequenceAvif = "Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound.avif"; + public const string Av1DifferenceWeightedCompoundSequenceNativeReference = "Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound-libaom.y4m"; + public const string Av1DifferenceWeightedCompoundSequencePresentationReference = "Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound-libavif.png"; + public const string Av1InterIntraSequenceAvif = "Heif/Av1/Conformance/libavif-webp-logo-inter-intra.avif"; + public const string Av1InterIntraSequenceNativeReference = "Heif/Av1/Conformance/libavif-webp-logo-inter-intra-libaom.y4m"; + public const string Av1InterIntraSequencePresentationReference = "Heif/Av1/Conformance/libavif-webp-logo-inter-intra-libavif.png"; public const string Av1Cdef8BitPayload = "Heif/Av1/Conformance/libaom-cdef-kodim23-8b.bit"; public const string Av1Cdef8BitReference = "Heif/Av1/Conformance/libaom-cdef-kodim23-8b-libaom.yuv"; public const string Av1Cdef8BitAvif = "Heif/Av1/Conformance/libavif-cdef-kodim23-8b.avif"; diff --git a/tests/Images/Input/Heif/Av1/Conformance/README.md b/tests/Images/Input/Heif/Av1/Conformance/README.md index d0d579db69..8cfa1250d2 100644 --- a/tests/Images/Input/Heif/Av1/Conformance/README.md +++ b/tests/Images/Input/Heif/Av1/Conformance/README.md @@ -19,6 +19,7 @@ The reference builds use `AOM_TARGET_CPU=generic` and disable libyuv. Native rec - `.bit` files contain the exact AV1 elementary-stream payload used by reconstruction tests. - `-libaom.yuv` files contain headerless planar Y, U, and V reference samples. Samples above eight bits are stored as little-endian 16-bit values. - `-libaom-y4m.yuv` files retain the Y4M header together with the native planar frame. +- `-libaom.y4m` files retain the Y4M header together with the selected native sequence frame. - `.png` files contain the eight-bit RGBA presentation reference produced by the pinned scalar libavif build. ## Coverage @@ -37,6 +38,10 @@ The reference builds use `AOM_TARGET_CPU=generic` and disable libyuv. Native rec | `*-film-grain-*` | Full and restricted range, monochrome, identity matrix, 8/10/12-bit synthesis, overlap, and odd frame dimensions | | `libavif-progressive-draw-points-8b` | A real two-layer color item whose final frame uses single-reference inter reconstruction, plus its progressive auxiliary alpha item | | `libavif-webp-logo-average-compound` | A 19-frame YUV444 image sequence whose retained references reach equal-weight compound inter reconstruction | +| `libavif-webp-logo-distance-weighted-compound` | Selectable distance-weighted compound prediction | +| `libavif-webp-logo-wedge-compound` | Wedge compound prediction with both signaled mask orientations | +| `libavif-webp-logo-difference-weighted-compound` | Difference-weighted compound prediction with both mask orientations | +| `libavif-webp-logo-inter-intra` | Smooth and wedge inter-intra prediction | The corresponding tests also assert the syntax required by each family before comparing output. This prevents an inactive tool or an incorrectly substituted stream from passing solely because its final pixels happen to match. @@ -69,6 +74,33 @@ Pinned scalar libavif generated the retained references with: The AVIF SHA-256 is `7919049D367EEDB7C965E170309D6759660DDBFD4BB1AEF9496F9D66E314846A`. The retained frame-18 Y4M SHA-256 is `41FF2408DEB473D5483F3398882DF7F7AB6C7D376561C19798881595EB0C5C0C`, and the frame-18 PNG SHA-256 is `BCFABC1E1C7E17D8ECB40569849A04FFAC6CA1FCDF613F217B33816CA47337AC`. The test decodes every preceding hidden and shown sample to establish the same retained-reference state before comparing all native Y, U, and V samples and the final RGBA presentation. +## Selectable compound and inter-intra fixtures + +The four selectable-compound fixtures use the same pinned `tests/data/webp_logo_animated.y4m` source and its `0872208D9C19B68B10A1647FA6849CFC4E2B21A19561ACD672E0629C70EFACA2` SHA-256. They were encoded at speed zero after disabling later inter-mode checkpoints. Each command also disables competing prediction tools that would prevent the resulting stream from isolating its named mode: + +```text +./avifenc -j 1 -c aom -s 0 -q 80 -a enable-obmc=0 -a enable-warped-motion=0 -a enable-global-motion=0 -a enable-masked-comp=0 -a enable-interintra-comp=0 tests/data/webp_logo_animated.y4m libavif-webp-logo-distance-weighted-compound.avif +./avifenc -j 1 -c aom -s 0 -q 80 -a enable-obmc=0 -a enable-warped-motion=0 -a enable-global-motion=0 -a enable-dist-wtd-comp=0 -a enable-diff-wtd-comp=0 -a enable-interintra-comp=0 tests/data/webp_logo_animated.y4m libavif-webp-logo-wedge-compound.avif +./avifenc -j 1 -c aom -s 0 -q 80 -a enable-obmc=0 -a enable-warped-motion=0 -a enable-global-motion=0 -a enable-dist-wtd-comp=0 -a enable-interinter-wedge=0 -a enable-interintra-comp=0 tests/data/webp_logo_animated.y4m libavif-webp-logo-difference-weighted-compound.avif +./avifenc -j 1 -c aom -s 0 -q 80 -a enable-obmc=0 -a enable-warped-motion=0 -a enable-global-motion=0 -a enable-dist-wtd-comp=0 -a enable-masked-comp=0 tests/data/webp_logo_animated.y4m libavif-webp-logo-inter-intra.avif +``` + +Pinned scalar libavif generated each final native and presentation reference with: + +```text +./avifdec -j 1 -c aom --index 18 .avif -libaom.y4m +./avifdec -j 1 -c aom --index 18 .avif -libavif.png +``` + +| Fixture | AVIF SHA-256 | Frame-18 Y4M SHA-256 | Frame-18 PNG SHA-256 | +| --- | --- | --- | --- | +| `libavif-webp-logo-distance-weighted-compound` | `DA710D11C60F03EEA209E4360E2FC807B89C49AD50671F0DFB1BCF4AD5EF76DD` | `904D1B5B3E7F334CE8D44040F9A7BDCAC1F7773122FF1C5F06A5B4DD31A62A97` | `D2CB388C9092EF17C4F0382C0150DD30D6F9D0EE247FF45AB5D7D4D312CEB23C` | +| `libavif-webp-logo-wedge-compound` | `98640640A445055FEA3D9E2F4A78FEEF54E97F8171B472CF57D99156E1C553B2` | `904D1B5B3E7F334CE8D44040F9A7BDCAC1F7773122FF1C5F06A5B4DD31A62A97` | `D2CB388C9092EF17C4F0382C0150DD30D6F9D0EE247FF45AB5D7D4D312CEB23C` | +| `libavif-webp-logo-difference-weighted-compound` | `FC6459CD334762D74D9D2654640221E80C463CC01B82B29A5866C9E725ABE273` | `904D1B5B3E7F334CE8D44040F9A7BDCAC1F7773122FF1C5F06A5B4DD31A62A97` | `D2CB388C9092EF17C4F0382C0150DD30D6F9D0EE247FF45AB5D7D4D312CEB23C` | +| `libavif-webp-logo-inter-intra` | `71DF22E63626B5BC9001FF1E88076B90F11BB47D18089750853E66F0CBBB084B` | `502265688138641A7B12C8C4190B66C76CD4808D9AED05B06486056B39D7E9A0` | `F0DE4CCDFB6D95A400E69B69FA4C57F0BEEEEE75825722C31613385F0B3FD9FC` | + +Pinned libaom block tracing confirms that these streams select distance weighting, both wedge signs, both difference-mask types, and both smooth and wedge inter-intra prediction. The production test independently requires those decoded mode states, decodes all preceding samples, compares the final native Y, U, and V planes exactly, compares the final RGBA presentation exactly, and repeats reconstruction with constrained tracked allocation. + ## Updating fixtures Do not create conformance references with ImageSharp. Generate both the native-plane and presentation references with an independent decoder, record the exact upstream revisions and source license, and preserve exact comparisons. A new tool-specific fixture should demonstrate that the relevant syntax is active and should be no larger than required to cover that behavior. diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound-libaom.y4m b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound-libaom.y4m new file mode 100644 index 0000000000..2d3f2f87e2 --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound-libaom.y4m @@ -0,0 +1,3 @@ +YUV4MPEG2 W80 H80 F25:1 Ip A0:0 C444 XYSCSS=444 XCOLORRANGE=LIMITED +FRAME +>>>>>>>>>>TTTTTTTTTTTv{UUs{U룱:>BB>Tctttttt^T?BCBBBBBBBBBBBBBBBBBBB\ssY>TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTcrsdT>ASVxWTB:Ti½\T:ITǻTJTi˽\T-T˰jT--TɰjT--TȰjT--TȰjT--TȰjT--TȰjT--TȰjT--T{zjT--To\STTUUTXnjT--TgTUWz{VTUfjT--TzVUeeUVz~~jT--TtUTTTo~~~~jT--TzTVVTu~~~~~~cT--TVUTV|~~~~~~~cT--TbUT^~~~~~~~~~cT--TTdeTz~~~~~~~~~~}cT--TeUUf~~~~~~~~~}}|cT--TWVVV~~~~~~~~~~}|||cT--TT{{St~~~~~~~~~~~||||{cT--TyTTn~~~~~~~~~~}|||{|z}cT--TsTTo~~~~~~~}|||||{zzy}cT--TtTTn~~~~~~}|||||zyyyy|}^T--TzTTn~~~}}}||||{yyyyxxz}_T--T|T{|Tu~~}}|||||yyyyyyuuy}]T--TVVWW||||||||yyyyyyttttyz^T--TfUT^||||||yyyyyyytttttty^T--TyTlkTm||||yyyyyyytttttttty^T--T_TS\||||yyyyyyttttttttrtt^T--TwzUVVTt{{yyyyyytttttttttqqqs^T--TwnUddTj}yzyyyyyttttttttqqqqqq^T--TwgTVVTf|zyyyyyttttttttqqqqqqqq^T--TwnTTllTTjyyyyyyutttttttqqqqqqqqqq^T--Tw~t]TTWWST\nyyyyyyttttttttqqqqqqqqqqq^T--Tw~~~~}n_TTUUTTSS]kyyyyyyttttttttqqqqqqqqqqqqq^T--Tw~~~~~~~~~xojegmotyyyyyyttttttttqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~|||||{{zyyyyyttttttttqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~~}|||||yyyyyyyttttttttqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~~||||||yyyyyyyttttttttqqqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~}||||||yyyyyyttttttttqqqqqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~|||||||yyyyyytttttttttqqqqqqqqqqqqqqqqqqqqqqq^T-.Uo~~~~~~~~~~|||||{yyyyyyyttttttttqqqqqqqqqqqqqqqqqqqqqqqqq]T-,Uj~~~~~~~~~~||||||zyyyyyyttttttttrqqqqqqqqqqqqqqqqqqqqqqqqqq]T-T\~~~~~~~~~}}|||||zzyyyyttttttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqq]TTT|||||zzyyyytttttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnTT>T\}}||yyyyttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnWT>JTWovwvvvvvvvvvvvvvwnnvnnnjnnnnjjfggggggggggggggggggggggggggggggcWTJBTUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTUC>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB>뀀")(("#")37883)$"37?FFE>72"efffffffff"*7BKRWRLB7)!\[[[[[[[[[""3?LWbcbXL?3#[MBBBBBBN[")7FSbouj_RB8)"[MBBBBBBN[+&#)6"$/7AIP\gd\\[[]_____________^^^^]]]]\\\\\\\\\\\\]UFB;6/(.:1)-))!!"'&"")*-3778@GPTU[oo[]bJAAAAAAAAAAAA@@@@@@@@@@@@@D@DDDDDDDDDDDDD>613.-**)))))*.3768BGGMTU[oo[^aJAAAAAAAAAAA@@@@@@@@@@@@D@DDDDDDDDDDDDDDD:=511*-------31678BGGGMTU[oo[^aJAAAAAAAAAA@@@@@@??????CDDDDDDDDDDDDDDDDDDB>661112222166=8BGGGHMTU[oo[^aJAAAAAAAA@@@@@@@?????C?DDDDDDDDDDDDDDDDDBBBB:==6666666<8BFGGGGGMTU[oo[^aJAAAAAAA@@@@@@@@@@@D@DDDDDDDDDDDDDDCDDDBBBBBBBB98=<=8BBHGGGGGFGMTU[oo[^aJAAAAA@A@@@@@@@@@@DDDDDDDDDDDDDDEDDDDBBBBBBBBBB?FAF@GGHGGGGGFFFMTU[oo[^^JAAA@A@@@@@@@@@@DDDDDDDDDDDDDDMMNKFBBBBBBBBBB??@GGGGGHGGGGFFFFFMTU[oo[^^JA@@@@@@@@@@@@D@DDDDDDDDDCGPZ[[[[[[XRJBBBBB@@HAHHHGGGGGGFFFFFFFLTU[oo[^^J@@@@@@@@@@@D@DDDDDDDDDDERZ[\fgjjgf[[\RDCB>H@GGHGGGGGGGFFFFFFCCLST[oo[^^J@@@@@@@@@@DDDDDDDDDDDDI\[\q||q\[\NG@GGGGGGGGGGFFFFFFCCAALST[oo[^^J@@@@@@@@DDDDDDDDDDCDDN[[n~n[[QFGGGGGGGGFFFFFFFCAAAALRT[oo[]^J@@@@@@D@DDDDDDDDDCDDH[[tt[[MGGGGGGFFFFFFFCAAAAAALRS[oo[]]J@@@@D@DDDDDDDDDDDDDF\[ut\]JGGGGFFFFFFCCAAAAAAALQS[oo[]]J@@@DDDDDDDDDDDDDDDDR\mm\SGGGEFFFFCCAAAAAAAAAKQSZoo[]]J@E@DDDDDDDDDDDDDDDK[^]\IFFGFFFCAAAAAAAAAAKKQSZoo[]^J@DDDDDDDDDDDDDDDDDS[qq[RGEFFCCAAAAAAAAJJJJJPSZoo[]\JDDDDDDDDDDDDDDDCDBW[~[]CFCCAAAAAAAAAAJJJJJOSZoo[\YJDDDDDDDDDDDDDDDBCK\ed[FCAAAAAAAAAAJJJJJJJMS[oo[\YJDDDDDDDDDDDDDBBBBG[kk\JAAAAAAAAAJJJJJJJIJLS[oo[[YJDDDDDDDDDDDDBBBBBM[nm[QAAAAAAAJJJJJJJHHHJKS[oo[[YJDDDDDDDDDDBBBBBBAM[om[QAAAAAAJJJJJJHHHHHJJS[oo[[YJDDDDDDDDBBBBBBBACN[kj[JABAAJJJJJJHHHHHHHHIS[oo[[YJDDDDDDDBBBBBBBBA?I[edZKBKJJJJJJJHHHHHHGGHIS[oo[[YIDDDCDBCBBBBBBBB@FH[\\WJIJJJJJJHHHHHHHGFFFHS[oo[[WIDDDBCBBBBBBBB@G@GGRZuq[TJJJJJJHHHHHHHGFFFFFHS[oo[[VIDCCBBBBBBBB@@@GFGGNZb~b[KJJIIHHHHHHHGFFFFFFGHS[oo[[UIBBBBBBBBB@@G@GGGGGGSZqqZYJJIIHHHHHHFFFFFFFGHHMS[oo[[UIBBBBBBBB@G@GGGGGGGGH[\vv\ZNIIHHHHHHFFFFFFFFGHHHGS[oo[ZUIBBCBAB@@@GGGGGGGGGGGJZ]ww][RJIHHHHHHFFFFFFFGHHHHHIS[op[ZVHBCBBA@?GGGGGGGGFFFFGFR[[qq[[RJHHHHHHFFFFFFFGHHHHHHHIS[op[ZVHBBBAF@GGGGGGGGGFFFFFGFR[[bttb[[QHIHHHHFFFFFFFFHHHHHHHHHIS[op[ZVHBAG@GGGGGGGGGGFFFFFFCCALZ[\\goqqog\[[YKHIHHHHFFFFFFFFHHHHHHHHHHIS[oo[ZUHAAGGGGGGGGGGFFFFFFCCAAAA@JS\\[[[[[[SPHHHHHHGFFFFFFFHHHHHHHHHHHHIS[oo[ZUHHGGGGGGGGGFFFFFFFCAAAAAAAA@JJRRRQIHHHHHHHFFFFFFFFHHHHHHHHHHHHHHIS[oo[ZUGGGGGGGGGFFFFFFFCBAAAAAAAAAJJJJJJIHHHHHHFFFFFFFGHHHHHHHHHHHHHHHHIS[oo[ZTGGGGGGGGFFFFFFBBAAAAAAAAAAJJJJJIIIHHHHHEFFFFFFGGHHHHHHHHHHHHHHHHIS[oo[ZRGGGGGGGFFFFFFBAAAAAAAAAAKJJJJIIIHHHHIEEFFFFFGGHHHHHHHHHHHHHHHHHHIS[oo[ZQGGGGGGFFFFFBAAAAAAAAAJJJJJJIIHHHHHIEEFFFFFGGHHHHHHHHHHHHHHHHHHHHIS[oo[ZQGGGGGGFFFBAAAAAAAAAAJJJJJJIIHHHHIEFFFFFFFGHHHHIHHHHHHHHHHHHHHHHHIS[op[VPFFFFFFFDAAAAAAAAAABKJJJIHHHHHHHHFFFFFFFFHHHHHHHHHHHHHHHHHHHHHHHHHS[pp[VPKGFFFFDAAAAAAAAAAJJJJJIIHHHHHHFFFFFFGGGGHHHHHHHHHHHHHHHHHHHHHHHHHW[p[YPMFFGCAAAAAAAAAKKJJJJJJIHHHHHFFFFFFFFHHHHHHHHHHHHHHHHHHHHHHHHHHHHHW[[[PQNLEFFGJAAKKKKKKKKJJJJIHHHHHFFFFFFFGHHHHHHHHHHHHHHHHHHHHHHHHHHHHH[[e[YQQQQQQLLLLLLKKKKKKKKKKIIIIHHHHFFFFHHHHHHHHHHHHHHHHHHHHHHHHHHHHHMW[f`[WRNSMLMSSMMMMMMSRRQQMQQQQQQQQQQQQPPPPPOOOOOOOOOOOOOOOOOOOOOOMMSW[ce[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[Z[[[[[[[[[[[[[[[[[[[[[[[[[[[[[cfcbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccfvwwwvvvvvvqqqqppppppqnddddddmppjddddddmpuuutvqpjjjjjmopttttttttttttttttttttttx}}}vpppppppppppppppppppppppppppppppppppppqw}}wpvtpqqqrrrrrrrrrrrrrrrrssssssrqpppppppppppqy}|vqpptvorqrrssssstttttttttssrrrrrrrrrrrrsssssporx~yvonnpvtptrojiggggggggggggggggggggggggggggggggggirq{{tplplptprsodddddddddddddddddheghhhhhhhhhggggggggglry|{vokklmopzprsidddddddddddddddddhhhhhhhhhhggggggggggggkrz}{vtqiemnopzzprsiddddddddddddeddhhhhhhhhhhghgggggggggggggmty{{vtqkfeklopzzprsiddddddddddfdhefhhhhhhhhhgggggggggggggggflotyy|{yttpkffgjlopzzprsiddddddddddhdfhhhhhhhhhgggggggggggggggggggjltuzyz{{{{zxtpqjffffklopzzprsiddddddddddfhhhhhhhhhggggggggggggggggggggggglopustttttppkefffffklopzzprtiddddddddghhhhhhhhhhggggggggggggggggggggggggggjlooooqkkeffffhhhllopzzprtidddddhdghhhhhhhhhgggggggggggggggggggggggggggggfgghgffffffgghiillopzzprsidddhdhhhhhhhhhhggggggggggggggjjjihgggggggggggggeeeeffffggigiihklopzzprsgdddhhhhhhhhhhggggggggggggimpppoopppmiggggggffffeeffffffiiiiiiiklopzzpqshddhhhhhhhhhhgggggggggggimppptvwwvtpppmigggfffffffffffghiiiiiiiklopzzpqrhhhhhhhhhhhggggggggggggjpprz~~zrpplfffffffffffghiiiiiiijjklopzzpqrhhhhhhhhhgggggggggggggjopyypomffffffffghiiiiiiijjjjjjopzzpprhhhhhhhggggggggggggggkppzzpojfffffggiiiiiiijjjjjjkjopzzppqhhhhhghgggggggggggggippzzqohffggiiiiiiiijjjjjjjkjopzzppphhhhggggggggggggggggppyxpogghhiiiiiijjjjjjjjjijopzzppphhghggggggggggggggggpssqlhhiiiiijjjjjjjjjjhhlopzzppphhggggggggggggggggglpyyplihhiiijjjjjjjjhhhhhlopzzppphggggggggggggggggggppppiiiiijjjjjjjjjhhhhllopzzppqhggggggggggggggghginttpjiijjjjjjjjjhhhhhhllopzzppqhggggggggggggggghgjowwpkjijjjjjjjhhhhhhhhllopzzppqhgggggggggggggggggjoxypljjjjjjjhhhhhhhhhhllopzzppqhgggggggggggggggggjpxypljjjjjjhhhhhhhhhhhhlopzzqpqhgggggggggggggggggjpvwpkjjjjhhhhhhhhhhhhhhlopzzqpphgggggggggggggggggiprtpjjihhhhhhhhhhhhhhihlopzzqpphggggggggggggggggfeqpoqihhhhhhhhhhhhhhiiiilopzzqpphggggggggggggggfffelp{{pohhhhhhhhhhhhhiiiiiikopzzqpphgggggggggggggfffffkqqspjhhhhhhhhhhhiiiiiiiikopzzqpphgggggggggggffeefffeqqy~wpohhhhhhhhhhiiiiiiiiihjopzzqpphgggggggggfgeeffffggiqp}}ppihhhhhhhhiiiiiiiiihhhjopzzqpphgggggggggeeefffffggijqr}}tpihhhhhhhhiiiiiiiihhhhhjopzzqpphgggggggeeeeffffefhihimppzzppkhhhhhhhiiiiiiiihhhhhhhiopzzqpphgggggggeeefffffiiiiiiimppr{{rppihhhhhhiiiiiiiihhhhhhhhhiopzzponiggfgfffffffffghiiiiiiijlnpppvvyyvvpppnlhhhhhhiiiiiiiihhhhhhhhhhiopzzpoliggfffffffffghhhiiiiijjjjjjoppppppppojhhhhhhiiiiiiiihhhhhhhhhhhhiopzzpolifffffffffghiiiiiiijjjjjjjjjikkkkkkhhhhhhhiiiiiiiihhhhhhhhhhhhhhiopzzqplifffffffghiiiiiiiijjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhiopzzqplifffffffiiiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiihhhhhhhhhhhhhhhhhhiopzzqplifffffifiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhiopzzpplifffiiiiiiiijjjjjjjjjihhhhhhhhhhhhhiiiiiiiiihhhhhhhhhhhhhhhhhhhhiopzzppliffiiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhiopzzpoliiiiiiiijjjjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhiopzzpolkiiiiiiijjjjjjjjjjhhhhhhhhhhhhiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhiopzpolkjiiiiiijjjjjjihihhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhiopppmjkjiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhippupnjjiiiijkkkkklllllllllllllhlhhhiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhjopvspomnmnmmmmnnnnnnlllllmmnnimmmmiimljiiiiiiiiiiiiiiiiiiiiiiiiiijjopptupppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppptvuuttttttttttttttttttttttttttttttttttttttttttttttttttttttttttv \ No newline at end of file diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound-libavif.png b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound-libavif.png new file mode 100644 index 0000000000..c94b799dc6 --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound-libavif.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2cb388c9092ef17c4f0382c0150dd30d6f9d0ee247ff45ab5d7d4d312ceb23c +size 4544 diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound.avif b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound.avif new file mode 100644 index 0000000000..c91aaa7010 --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-difference-weighted-compound.avif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc6459cd334762d74d9d2654640221e80c463cc01b82b29a5866c9e725abe273 +size 6423 diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound-libaom.y4m b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound-libaom.y4m new file mode 100644 index 0000000000..2d3f2f87e2 --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound-libaom.y4m @@ -0,0 +1,3 @@ +YUV4MPEG2 W80 H80 F25:1 Ip A0:0 C444 XYSCSS=444 XCOLORRANGE=LIMITED +FRAME +>>>>>>>>>>TTTTTTTTTTTv{UUs{U룱:>BB>Tctttttt^T?BCBBBBBBBBBBBBBBBBBBB\ssY>TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTcrsdT>ASVxWTB:Ti½\T:ITǻTJTi˽\T-T˰jT--TɰjT--TȰjT--TȰjT--TȰjT--TȰjT--TȰjT--T{zjT--To\STTUUTXnjT--TgTUWz{VTUfjT--TzVUeeUVz~~jT--TtUTTTo~~~~jT--TzTVVTu~~~~~~cT--TVUTV|~~~~~~~cT--TbUT^~~~~~~~~~cT--TTdeTz~~~~~~~~~~}cT--TeUUf~~~~~~~~~}}|cT--TWVVV~~~~~~~~~~}|||cT--TT{{St~~~~~~~~~~~||||{cT--TyTTn~~~~~~~~~~}|||{|z}cT--TsTTo~~~~~~~}|||||{zzy}cT--TtTTn~~~~~~}|||||zyyyy|}^T--TzTTn~~~}}}||||{yyyyxxz}_T--T|T{|Tu~~}}|||||yyyyyyuuy}]T--TVVWW||||||||yyyyyyttttyz^T--TfUT^||||||yyyyyyytttttty^T--TyTlkTm||||yyyyyyytttttttty^T--T_TS\||||yyyyyyttttttttrtt^T--TwzUVVTt{{yyyyyytttttttttqqqs^T--TwnUddTj}yzyyyyyttttttttqqqqqq^T--TwgTVVTf|zyyyyyttttttttqqqqqqqq^T--TwnTTllTTjyyyyyyutttttttqqqqqqqqqq^T--Tw~t]TTWWST\nyyyyyyttttttttqqqqqqqqqqq^T--Tw~~~~}n_TTUUTTSS]kyyyyyyttttttttqqqqqqqqqqqqq^T--Tw~~~~~~~~~xojegmotyyyyyyttttttttqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~|||||{{zyyyyyttttttttqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~~}|||||yyyyyyyttttttttqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~~||||||yyyyyyyttttttttqqqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~}||||||yyyyyyttttttttqqqqqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~|||||||yyyyyytttttttttqqqqqqqqqqqqqqqqqqqqqqq^T-.Uo~~~~~~~~~~|||||{yyyyyyyttttttttqqqqqqqqqqqqqqqqqqqqqqqqq]T-,Uj~~~~~~~~~~||||||zyyyyyyttttttttrqqqqqqqqqqqqqqqqqqqqqqqqqq]T-T\~~~~~~~~~}}|||||zzyyyyttttttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqq]TTT|||||zzyyyytttttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnTT>T\}}||yyyyttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnWT>JTWovwvvvvvvvvvvvvvwnnvnnnjnnnnjjfggggggggggggggggggggggggggggggcWTJBTUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTUC>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB>뀀")(("#")37883)$"37?FFE>72"efffffffff"*7BKRWRLB7)!\[[[[[[[[[""3?LWbcbXL?3#[MBBBBBBN[")7FSbouj_RB8)"[MBBBBBBN[+&#)6"$/7AIP\gd\\[[]_____________^^^^]]]]\\\\\\\\\\\\]UFB;6/(.:1)-))!!"'&"")*-3778@GPTU[oo[]bJAAAAAAAAAAAA@@@@@@@@@@@@@D@DDDDDDDDDDDDD>613.-**)))))*.3768BGGMTU[oo[^aJAAAAAAAAAAA@@@@@@@@@@@@D@DDDDDDDDDDDDDDD:=511*-------31678BGGGMTU[oo[^aJAAAAAAAAAA@@@@@@??????CDDDDDDDDDDDDDDDDDDB>661112222166=8BGGGHMTU[oo[^aJAAAAAAAA@@@@@@@?????C?DDDDDDDDDDDDDDDDDBBBB:==6666666<8BFGGGGGMTU[oo[^aJAAAAAAA@@@@@@@@@@@D@DDDDDDDDDDDDDDCDDDBBBBBBBB98=<=8BBHGGGGGFGMTU[oo[^aJAAAAA@A@@@@@@@@@@DDDDDDDDDDDDDDEDDDDBBBBBBBBBB?FAF@GGHGGGGGFFFMTU[oo[^^JAAA@A@@@@@@@@@@DDDDDDDDDDDDDDMMNKFBBBBBBBBBB??@GGGGGHGGGGFFFFFMTU[oo[^^JA@@@@@@@@@@@@D@DDDDDDDDDCGPZ[[[[[[XRJBBBBB@@HAHHHGGGGGGFFFFFFFLTU[oo[^^J@@@@@@@@@@@D@DDDDDDDDDDERZ[\fgjjgf[[\RDCB>H@GGHGGGGGGGFFFFFFCCLST[oo[^^J@@@@@@@@@@DDDDDDDDDDDDI\[\q||q\[\NG@GGGGGGGGGGFFFFFFCCAALST[oo[^^J@@@@@@@@DDDDDDDDDDCDDN[[n~n[[QFGGGGGGGGFFFFFFFCAAAALRT[oo[]^J@@@@@@D@DDDDDDDDDCDDH[[tt[[MGGGGGGFFFFFFFCAAAAAALRS[oo[]]J@@@@D@DDDDDDDDDDDDDF\[ut\]JGGGGFFFFFFCCAAAAAAALQS[oo[]]J@@@DDDDDDDDDDDDDDDDR\mm\SGGGEFFFFCCAAAAAAAAAKQSZoo[]]J@E@DDDDDDDDDDDDDDDK[^]\IFFGFFFCAAAAAAAAAAKKQSZoo[]^J@DDDDDDDDDDDDDDDDDS[qq[RGEFFCCAAAAAAAAJJJJJPSZoo[]\JDDDDDDDDDDDDDDDCDBW[~[]CFCCAAAAAAAAAAJJJJJOSZoo[\YJDDDDDDDDDDDDDDDBCK\ed[FCAAAAAAAAAAJJJJJJJMS[oo[\YJDDDDDDDDDDDDDBBBBG[kk\JAAAAAAAAAJJJJJJJIJLS[oo[[YJDDDDDDDDDDDDBBBBBM[nm[QAAAAAAAJJJJJJJHHHJKS[oo[[YJDDDDDDDDDDBBBBBBAM[om[QAAAAAAJJJJJJHHHHHJJS[oo[[YJDDDDDDDDBBBBBBBACN[kj[JABAAJJJJJJHHHHHHHHIS[oo[[YJDDDDDDDBBBBBBBBA?I[edZKBKJJJJJJJHHHHHHGGHIS[oo[[YIDDDCDBCBBBBBBBB@FH[\\WJIJJJJJJHHHHHHHGFFFHS[oo[[WIDDDBCBBBBBBBB@G@GGRZuq[TJJJJJJHHHHHHHGFFFFFHS[oo[[VIDCCBBBBBBBB@@@GFGGNZb~b[KJJIIHHHHHHHGFFFFFFGHS[oo[[UIBBBBBBBBB@@G@GGGGGGSZqqZYJJIIHHHHHHFFFFFFFGHHMS[oo[[UIBBBBBBBB@G@GGGGGGGGH[\vv\ZNIIHHHHHHFFFFFFFFGHHHGS[oo[ZUIBBCBAB@@@GGGGGGGGGGGJZ]ww][RJIHHHHHHFFFFFFFGHHHHHIS[op[ZVHBCBBA@?GGGGGGGGFFFFGFR[[qq[[RJHHHHHHFFFFFFFGHHHHHHHIS[op[ZVHBBBAF@GGGGGGGGGFFFFFGFR[[bttb[[QHIHHHHFFFFFFFFHHHHHHHHHIS[op[ZVHBAG@GGGGGGGGGGFFFFFFCCALZ[\\goqqog\[[YKHIHHHHFFFFFFFFHHHHHHHHHHIS[oo[ZUHAAGGGGGGGGGGFFFFFFCCAAAA@JS\\[[[[[[SPHHHHHHGFFFFFFFHHHHHHHHHHHHIS[oo[ZUHHGGGGGGGGGFFFFFFFCAAAAAAAA@JJRRRQIHHHHHHHFFFFFFFFHHHHHHHHHHHHHHIS[oo[ZUGGGGGGGGGFFFFFFFCBAAAAAAAAAJJJJJJIHHHHHHFFFFFFFGHHHHHHHHHHHHHHHHIS[oo[ZTGGGGGGGGFFFFFFBBAAAAAAAAAAJJJJJIIIHHHHHEFFFFFFGGHHHHHHHHHHHHHHHHIS[oo[ZRGGGGGGGFFFFFFBAAAAAAAAAAKJJJJIIIHHHHIEEFFFFFGGHHHHHHHHHHHHHHHHHHIS[oo[ZQGGGGGGFFFFFBAAAAAAAAAJJJJJJIIHHHHHIEEFFFFFGGHHHHHHHHHHHHHHHHHHHHIS[oo[ZQGGGGGGFFFBAAAAAAAAAAJJJJJJIIHHHHIEFFFFFFFGHHHHIHHHHHHHHHHHHHHHHHIS[op[VPFFFFFFFDAAAAAAAAAABKJJJIHHHHHHHHFFFFFFFFHHHHHHHHHHHHHHHHHHHHHHHHHS[pp[VPKGFFFFDAAAAAAAAAAJJJJJIIHHHHHHFFFFFFGGGGHHHHHHHHHHHHHHHHHHHHHHHHHW[p[YPMFFGCAAAAAAAAAKKJJJJJJIHHHHHFFFFFFFFHHHHHHHHHHHHHHHHHHHHHHHHHHHHHW[[[PQNLEFFGJAAKKKKKKKKJJJJIHHHHHFFFFFFFGHHHHHHHHHHHHHHHHHHHHHHHHHHHHH[[e[YQQQQQQLLLLLLKKKKKKKKKKIIIIHHHHFFFFHHHHHHHHHHHHHHHHHHHHHHHHHHHHHMW[f`[WRNSMLMSSMMMMMMSRRQQMQQQQQQQQQQQQPPPPPOOOOOOOOOOOOOOOOOOOOOOMMSW[ce[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[Z[[[[[[[[[[[[[[[[[[[[[[[[[[[[[cfcbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccfvwwwvvvvvvqqqqppppppqnddddddmppjddddddmpuuutvqpjjjjjmopttttttttttttttttttttttx}}}vpppppppppppppppppppppppppppppppppppppqw}}wpvtpqqqrrrrrrrrrrrrrrrrssssssrqpppppppppppqy}|vqpptvorqrrssssstttttttttssrrrrrrrrrrrrsssssporx~yvonnpvtptrojiggggggggggggggggggggggggggggggggggirq{{tplplptprsodddddddddddddddddheghhhhhhhhhggggggggglry|{vokklmopzprsidddddddddddddddddhhhhhhhhhhggggggggggggkrz}{vtqiemnopzzprsiddddddddddddeddhhhhhhhhhhghgggggggggggggmty{{vtqkfeklopzzprsiddddddddddfdhefhhhhhhhhhgggggggggggggggflotyy|{yttpkffgjlopzzprsiddddddddddhdfhhhhhhhhhgggggggggggggggggggjltuzyz{{{{zxtpqjffffklopzzprsiddddddddddfhhhhhhhhhggggggggggggggggggggggglopustttttppkefffffklopzzprtiddddddddghhhhhhhhhhggggggggggggggggggggggggggjlooooqkkeffffhhhllopzzprtidddddhdghhhhhhhhhgggggggggggggggggggggggggggggfgghgffffffgghiillopzzprsidddhdhhhhhhhhhhggggggggggggggjjjihgggggggggggggeeeeffffggigiihklopzzprsgdddhhhhhhhhhhggggggggggggimpppoopppmiggggggffffeeffffffiiiiiiiklopzzpqshddhhhhhhhhhhgggggggggggimppptvwwvtpppmigggfffffffffffghiiiiiiiklopzzpqrhhhhhhhhhhhggggggggggggjpprz~~zrpplfffffffffffghiiiiiiijjklopzzpqrhhhhhhhhhgggggggggggggjopyypomffffffffghiiiiiiijjjjjjopzzpprhhhhhhhggggggggggggggkppzzpojfffffggiiiiiiijjjjjjkjopzzppqhhhhhghgggggggggggggippzzqohffggiiiiiiiijjjjjjjkjopzzppphhhhggggggggggggggggppyxpogghhiiiiiijjjjjjjjjijopzzppphhghggggggggggggggggpssqlhhiiiiijjjjjjjjjjhhlopzzppphhggggggggggggggggglpyyplihhiiijjjjjjjjhhhhhlopzzppphggggggggggggggggggppppiiiiijjjjjjjjjhhhhllopzzppqhggggggggggggggghginttpjiijjjjjjjjjhhhhhhllopzzppqhggggggggggggggghgjowwpkjijjjjjjjhhhhhhhhllopzzppqhgggggggggggggggggjoxypljjjjjjjhhhhhhhhhhllopzzppqhgggggggggggggggggjpxypljjjjjjhhhhhhhhhhhhlopzzqpqhgggggggggggggggggjpvwpkjjjjhhhhhhhhhhhhhhlopzzqpphgggggggggggggggggiprtpjjihhhhhhhhhhhhhhihlopzzqpphggggggggggggggggfeqpoqihhhhhhhhhhhhhhiiiilopzzqpphggggggggggggggfffelp{{pohhhhhhhhhhhhhiiiiiikopzzqpphgggggggggggggfffffkqqspjhhhhhhhhhhhiiiiiiiikopzzqpphgggggggggggffeefffeqqy~wpohhhhhhhhhhiiiiiiiiihjopzzqpphgggggggggfgeeffffggiqp}}ppihhhhhhhhiiiiiiiiihhhjopzzqpphgggggggggeeefffffggijqr}}tpihhhhhhhhiiiiiiiihhhhhjopzzqpphgggggggeeeeffffefhihimppzzppkhhhhhhhiiiiiiiihhhhhhhiopzzqpphgggggggeeefffffiiiiiiimppr{{rppihhhhhhiiiiiiiihhhhhhhhhiopzzponiggfgfffffffffghiiiiiiijlnpppvvyyvvpppnlhhhhhhiiiiiiiihhhhhhhhhhiopzzpoliggfffffffffghhhiiiiijjjjjjoppppppppojhhhhhhiiiiiiiihhhhhhhhhhhhiopzzpolifffffffffghiiiiiiijjjjjjjjjikkkkkkhhhhhhhiiiiiiiihhhhhhhhhhhhhhiopzzqplifffffffghiiiiiiiijjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhiopzzqplifffffffiiiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiihhhhhhhhhhhhhhhhhhiopzzqplifffffifiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhiopzzpplifffiiiiiiiijjjjjjjjjihhhhhhhhhhhhhiiiiiiiiihhhhhhhhhhhhhhhhhhhhiopzzppliffiiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhiopzzpoliiiiiiiijjjjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhiopzzpolkiiiiiiijjjjjjjjjjhhhhhhhhhhhhiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhiopzpolkjiiiiiijjjjjjihihhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhiopppmjkjiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhippupnjjiiiijkkkkklllllllllllllhlhhhiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhjopvspomnmnmmmmnnnnnnlllllmmnnimmmmiimljiiiiiiiiiiiiiiiiiiiiiiiiiijjopptupppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppptvuuttttttttttttttttttttttttttttttttttttttttttttttttttttttttttv \ No newline at end of file diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound-libavif.png b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound-libavif.png new file mode 100644 index 0000000000..c94b799dc6 --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound-libavif.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2cb388c9092ef17c4f0382c0150dd30d6f9d0ee247ff45ab5d7d4d312ceb23c +size 4544 diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound.avif b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound.avif new file mode 100644 index 0000000000..d7ce8203b9 --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-distance-weighted-compound.avif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da710d11c60f03eea209e4360e2fc807b89c49ad50671f0dfb1bcf4ad5ef76dd +size 6437 diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-inter-intra-libaom.y4m b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-inter-intra-libaom.y4m new file mode 100644 index 0000000000..6d8fdcc925 --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-inter-intra-libaom.y4m @@ -0,0 +1,3 @@ +YUV4MPEG2 W80 H80 F25:1 Ip A0:0 C444 XYSCSS=444 XCOLORRANGE=LIMITED +FRAME +>>>>>>>>>>TTTTTTTTTTTv{UUs{U룱:>BB>Tctttttt^T?BCBBBBBBBBBBBBBBBBBBB\ssY>TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTcrsdT>ASVxWTB:Ti½\T:ITǻTJTi˽\T-T˰jT--TɰjT--TȰjT--TȰjT--TȰjT--TȰjT--TȰjT--T{zjT--To\STTUUTXnjT--TgTUWz{VTUfjT--TzVUeeUVz~~jT--TtUTTTo~~~~jT--TzTVVTu~~~~~~cT--TVUTV|~~~~~~~cT--TbUT^~~~~~~~~~cT--TTdeTz~~~~~~~~~~}cT--TeUUf~~~~~~~~~}}|cT--TWVVV~~~~~~~~~~}|||cT--TT{{St~~~~~~~~~~~||||{cT--TyTTn~~~~~~~~~~}|||{|z}cT--TsTTo~~~~~~~}|||||{zzy}cT--TtTTn~~~~~~}|||||zyyyy|}^T--TzTTn~~~}}}||||{yyyyxxz}_T--T|T{|Tu~~}}|||||yyyyyyuuy}]T--TVVWW||||||||yyyyyyttttyz^T--TfUT^||||||yyyyyyytttttty^T--TyTlkTm||||yyyyyyytttttttty^T--T_TS\||||yyyyyyttttttttrtt^T--TwzUVVTt{{yyyyyytttttttttqqqs^T--TwnUddTj}yzyyyyyttttttttqqqqqq^T--TwgTVVTf|zyyyyyttttttttqqqqqqqq^T--TwnTTllTTjyyyyyyutttttttqqqqqqqqqq^T--Tw~t]TTWWST\nyyyyyyttttttttqqqqqqqqqqq^T--Tw~~~~}n_TTUUTTSS]kyyyyyyttttttttqqqqqqqqqqqqq^T--Tw~~~~~~~~~xojegmotyyyyyyttttttttqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~|||||{{zyyyyyttttttttqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~~}|||||yyyyyyyttttttttqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~~||||||yyyyyyyttttttttqqqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~}||||||yyyyyyttttttttqqqqqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~|||||||yyyyyytttttttttqqqqqqqqqqqqqqqqqqqqqqq^T-.Uo~~~~~~~~~~|||||{yyyyyyyttttttttqqqqqqqqqqqqqqqqqqqqqqqqq]T-,Uj~~~~~~~~~~||||||zyyyyyyttttttttrqqqqqqqqqqqqqqqqqqqqqqqqqq]T-T\~~~~~~~~~}}|||||zzyyyyttttttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqq]TTT|||||zzyyyytttttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnTT>T\}}||yyyyttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnWT>JTWovwvvvvvvvvvvvvvwnnvnnnjnnnnjjfggggggggggggggggggggggggggggggcWTJBTUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTUC>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB>뀀")(("#")37883)$"37?FFE>72"efffffffff"*7BKRWRLB7)!\[[[[[[[[[""3?LWbcbXL?3#[MBBBBBBN[")7FSbouj_RB8)"[MBBBBBBN[+&#)6"$/7AIP\gd\\[[]_____________^^^^]]]]\\\\\\\\\\\\]UFB;6/(.:1)-))!!"'&"")*-3778@GPTU[oo[]bJAAAAAAAAAAAA@@@@@@@@@@@@@D@DDDDDDDDDDDDD>613.-**)))))*.3768BGGMTU[oo[^aJAAAAAAAAAAA@@@@@@@@@@@@D@DDDDDDDDDDDDDDD:=511*-------31678BGGGMTU[oo[^aJAAAAAAAAAA@@@@@@??????CDDDDDDDDDDDDDDDDDDB>661112222166=8BGGGHMTU[oo[^aJAAAAAAAA@@@@@@@?????C?DDDDDDDDDDDDDDDDDBBBB:==6666666<8BFGGGGGMTU[oo[^aJAAAAAAA@@@@@@@@@@@D@DDDDDDDDDDDDDDCDDDBBBBBBBB98=<=8BBHGGGGGFGMTU[oo[^aJAAAAA@A@@@@@@@@@@DDDDDDDDDDDDDDEDDDDBBBBBBBBBB?FAF@GGHGGGGGFFFMTU[oo[__JAAA@A@@@@@@@@@@DDDDDDDDDDDDDDMMNKFBBBBBBBBBB??@GGGGGHGGGGFFFFFMTU[oo[__JA@@@@@@@@@@@@D@DDDDDDDDDCGPZ[[[[[[XRJBBBBB@@HAHHHGGGGGGFFFFFFFLTU[oo[__J@@@@@@@@@@@D@DDDDDDDDDDERZ[\fgjjgf[[\RDCB>H@GGHGGGGGGGFFFFFFCCLST[oo[^^J@@@@@@@@@@DDDDDDDDDDDDI\[\q||q\[\NG@GGGGGGGGGGFFFFFFCCAALST[oo[]^J@@@@@@@@DDDDDDDDDDCDDN[[n~n[[QFGGGGGGGGFFFFFFFCAAAALRT[oo[]]J@@@@@@D@DDDDDDDDDCDDH[[tt[[MGGGGGGFFFFFFFCAAAAAALRS[oo[]]J@@@@D@DDDDDDDDDDDDDF\[ut\]JGGGGFFFFFFCCAAAAAAALQS[oo[]]J@@@DDDDDDDDDDDDDDDDR\mm\SGGGEFFFFCCAAAAAAAAAKQSZoo[]^J@E@DDDDDDDDDDDDDDDK[^]\IFFGFFFCAAAAAAAAAAKKQSZoo[]^J@DDDDDDDDDDDDDDDDDS[qq[RGEFFCCAAAAAAAAJJJJJPSZoo[]\JDDDDDDDDDDDDDDDCDBW[~[]CFCCAAAAAAAAAAJJJJJOSZoo[\YJDDDDDDDDDDDDDDDBCK\ed[FCAAAAAAAAAAJJJJJJJMS[oo[\YJDDDDDDDDDDDDDBBBBG[kk\JAAAAAAAAAJJJJJJJIJLS[oo[[YJDDDDDDDDDDDDBBBBBM[nm[QAAAAAAAJJJJJJJHHHJKS[oo[[YJDDDDDDDDDDBBBBBBAM[om[QAAAAAAJJJJJJHHHHHJJS[oo[[YJDDDDDDDDBBBBBBBACN[kj[JABAAJJJJJJHHHHHHHHIS[oo[[YJDDDDDDDBBBBBBBBA?I[edZKBKJJJJJJJHHHHHHGGHIS[oo[[YIDDDCDBCBBBBBBBB@FH[\\WJIJJJJJJHHHHHHHGFFFHS[oo[[WIDDDBCBBBBBBBB@G@GGRZuq[TJJJJJJHHHHHHHGFFFFFHS[oo[[VIDCCBBBBBBBB@@@GFGGNZb~b[KJJIIHHHHHHHGFFFFFFGHS[oo[[UIBBBBBBBBB@@G@GGGGGGSZqqZYJJIIHHHHHHFFFFFFFGHHMS[oo[[UIBBBBBBBB@G@GGGGGGGGH[\vv\ZNIIHHHHHHFFFFFFFFGHHHGS[oo[ZUIBBCBAB@@@GGGGGGGGGGGJZ]ww][RJIHHHHHHFFFFFFFGHHHHHIS[op[ZVHBCBBA@?GGGGGGGGFFFFGFR[[qq[[RJHHHHHHFFFFFFFGHHHHHHHIS[op[ZVHBBBAF@GGGGGGGGGFFFFFGFR[[bttb[[QHIHHHHFFFFFFFFHHHHHHHHHIS[op[ZVHBAG@GGGGGGGGGGFFFFFFCCALZ[\\goqqog\[[YKHIHHHHFFFFFFFFHHHHHHHHHHIS[oo[ZUHAAGGGGGGGGGGFFFFFFCCAAAA@JS\\[[[[[[SPHHHHHHGFFFFFFFHHHHHHHHHHHHIS[oo[ZUHHGGGGGGGGGFFFFFFFCAAAAAAAA@JJRRRQIHHHHHHHFFFFFFFFHHHHHHHHHHHHHHIS[oo[ZUGGGGGGGGGFFFFFFFCBAAAAAAAAAJJJJJJIHHHHHHFFFFFFFGHHHHHHHHHHHHHHHHIS[oo[ZTGGGGGGGGFFFFFFBBAAAAAAAAAAJJJJJIIIHHHHHEFFFFFFGGHHHHHHHHHHHHHHHHIS[oo[ZRGGGGGGGFFFFFFBAAAAAAAAAAKJJJJIIIHHHHIEEFFFFFGGHHHHHHHHHHHHHHHHHHIS[oo[ZQGGGGGGFFFFFBAAAAAAAAAJJJJJJIIHHHHHIEEFFFFFGGHHHHHHHHHHHHHHHHHHHHIS[oo[ZQGGGGGGFFFBAAAAAAAAAAJJJJJJIIHHHHIEFFFFFFFGHHHHIHHHHHHHHHHHHHHHHHIS[op[VPFFFFFFFDAAAAAAAAAABKJJJIHHHHHHHHFFFFFFFFHHHHHHHHHHHHHHHHHHHHHHHHHS[pp[VPKGFFFFDAAAAAAAAAAJJJJJIIHHHHHHFFFFFFGGGGHHHHHHHHHHHHHHHHHHHHHHHHHW[p[YPMFFGCAAAAAAAAAKKJJJJJJIHHHHHFFFFFFFFHHHHHHHHHHHHHHHHHHHHHHHHHHHHHW[[[PQNLEFFGJAAKKKKKKKKJJJJIHHHHHFFFFFFFGHHHHHHHHHHHHHHHHHHHHHHHHHHHHH[[e[YQQQQQQLLLLLLKKKKKKKKKKIIIIHHHHFFFFHHHHHHHHHHHHHHHHHHHHHHHHHHHHHMW[f`[WRNSMLMSSMMMMMMSRRQQMQQQQQQQQQQQQPPPPPOOOOOOOOOOOOOOOOOOOOOOMMSW[ce[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[Z[[[[[[[[[[[[[[[[[[[[[[[[[[[[[cfcbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccfvwwwvvvvvvqqqqppppppqnddddddmppjddddddmpuuutvqpjjjjjmopttttttttttttttttttttttx}}}vpppppppppppppppppppppppppppppppppppppqw}}wpvtpqqqrrrrrrrrrrrrrrrrssssssrqpppppppppppqy}|vqpptvorqrrssssstttttttttssrrrrrrrrrrrrsssssporx~yvonnpvtptrojiggggggggggggggggggggggggggggggggggirq{{tplplptprsodddddddddddddddddheghhhhhhhhhggggggggglry|{vokklmopzprsidddddddddddddddddhhhhhhhhhhggggggggggggkrz}{vtqiemnopzzprsiddddddddddddeddhhhhhhhhhhghgggggggggggggmty{{vtqkfeklopzzprsiddddddddddfdhefhhhhhhhhhgggggggggggggggflotyy|{yttpkffgjlopzzprsiddddddddddhdfhhhhhhhhhgggggggggggggggggggjltuzyz{{{{zxtpqjffffklopzzprsiddddddddddfhhhhhhhhhggggggggggggggggggggggglopustttttppkefffffklopzzprtiddddddddghhhhhhhhhhggggggggggggggggggggggggggjlooooqkkeffffhhhllopzzprtidddddhdghhhhhhhhhgggggggggggggggggggggggggggggfgghgffffffgghiillopzzprtidddhdhhhhhhhhhhggggggggggggggjjjihgggggggggggggeeeeffffggigiihklopzzprtgdddhhhhhhhhhhggggggggggggimpppoopppmiggggggffffeeffffffiiiiiiiklopzzprshddhhhhhhhhhhgggggggggggimppptvwwvtpppmigggfffffffffffghiiiiiiiklopzzpqshhhhhhhhhhhggggggggggggjpprz~~zrpplfffffffffffghiiiiiiijjklopzzpqrhhhhhhhhhgggggggggggggjopyypomffffffffghiiiiiiijjjjjjopzzppqhhhhhhhggggggggggggggkppzzpojfffffggiiiiiiijjjjjjkjopzzppphhhhhghgggggggggggggippzzqohffggiiiiiiiijjjjjjjkjopzzppphhhhggggggggggggggggppyxpogghhiiiiiijjjjjjjjjijopzzppphhghggggggggggggggggpssqlhhiiiiijjjjjjjjjjhhlopzzppphhggggggggggggggggglpyyplihhiiijjjjjjjjhhhhhlopzzppphggggggggggggggggggppppiiiiijjjjjjjjjhhhhllopzzppqhggggggggggggggghginttpjiijjjjjjjjjhhhhhhllopzzppqhggggggggggggggghgjowwpkjijjjjjjjhhhhhhhhllopzzppqhgggggggggggggggggjoxypljjjjjjjhhhhhhhhhhllopzzppqhgggggggggggggggggjpxypljjjjjjhhhhhhhhhhhhlopzzqpqhgggggggggggggggggjpvwpkjjjjhhhhhhhhhhhhhhlopzzqpphgggggggggggggggggiprtpjjihhhhhhhhhhhhhhihlopzzqpphggggggggggggggggfeqpoqihhhhhhhhhhhhhhiiiilopzzqpphggggggggggggggfffelp{{pohhhhhhhhhhhhhiiiiiikopzzqpphgggggggggggggfffffkqqspjhhhhhhhhhhhiiiiiiiikopzzqpphgggggggggggffeefffeqqy~wpohhhhhhhhhhiiiiiiiiihjopzzqpphgggggggggfgeeffffggiqp}}ppihhhhhhhhiiiiiiiiihhhjopzzqpphgggggggggeeefffffggijqr}}tpihhhhhhhhiiiiiiiihhhhhjopzzqpphgggggggeeeeffffefhihimppzzppkhhhhhhhiiiiiiiihhhhhhhiopzzqpphgggggggeeefffffiiiiiiimppr{{rppihhhhhhiiiiiiiihhhhhhhhhiopzzponiggfgfffffffffghiiiiiiijlnpppvvyyvvpppnlhhhhhhiiiiiiiihhhhhhhhhhiopzzpoliggfffffffffghhhiiiiijjjjjjoppppppppojhhhhhhiiiiiiiihhhhhhhhhhhhiopzzpolifffffffffghiiiiiiijjjjjjjjjikkkkkkhhhhhhhiiiiiiiihhhhhhhhhhhhhhiopzzqplifffffffghiiiiiiiijjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhiopzzqplifffffffiiiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiihhhhhhhhhhhhhhhhhhiopzzqplifffffifiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhiopzzpplifffiiiiiiiijjjjjjjjjihhhhhhhhhhhhhiiiiiiiiihhhhhhhhhhhhhhhhhhhhiopzzppliffiiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhiopzzpoliiiiiiiijjjjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhiopzzpolkiiiiiiijjjjjjjjjjhhhhhhhhhhhhiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhiopzpolkjiiiiiijjjjjjihihhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhiopppmjkjiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhippupnjjiiiijkkkkklllllllllllllhlhhhiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhjopvspomnmnmmmmnnnnnnlllllmmnnimmmmiimljiiiiiiiiiiiiiiiiiiiiiiiiiijjopptupppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppptvuuttttttttttttttttttttttttttttttttttttttttttttttttttttttttttv \ No newline at end of file diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-inter-intra-libavif.png b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-inter-intra-libavif.png new file mode 100644 index 0000000000..9b72f7f9c1 --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-inter-intra-libavif.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0de4ccdfb6d95a400e69b69fa4c57f0beeeee75825722c31613385f0b3fd9fc +size 4551 diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-inter-intra.avif b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-inter-intra.avif new file mode 100644 index 0000000000..6311f8e22d --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-inter-intra.avif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71df22e63626b5bc9001ff1e88076b90f11bb47d18089750853e66f0cbbb084b +size 6392 diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-wedge-compound-libaom.y4m b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-wedge-compound-libaom.y4m new file mode 100644 index 0000000000..2d3f2f87e2 --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-wedge-compound-libaom.y4m @@ -0,0 +1,3 @@ +YUV4MPEG2 W80 H80 F25:1 Ip A0:0 C444 XYSCSS=444 XCOLORRANGE=LIMITED +FRAME +>>>>>>>>>>TTTTTTTTTTTv{UUs{U룱:>BB>Tctttttt^T?BCBBBBBBBBBBBBBBBBBBB\ssY>TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTcrsdT>ASVxWTB:Ti½\T:ITǻTJTi˽\T-T˰jT--TɰjT--TȰjT--TȰjT--TȰjT--TȰjT--TȰjT--T{zjT--To\STTUUTXnjT--TgTUWz{VTUfjT--TzVUeeUVz~~jT--TtUTTTo~~~~jT--TzTVVTu~~~~~~cT--TVUTV|~~~~~~~cT--TbUT^~~~~~~~~~cT--TTdeTz~~~~~~~~~~}cT--TeUUf~~~~~~~~~}}|cT--TWVVV~~~~~~~~~~}|||cT--TT{{St~~~~~~~~~~~||||{cT--TyTTn~~~~~~~~~~}|||{|z}cT--TsTTo~~~~~~~}|||||{zzy}cT--TtTTn~~~~~~}|||||zyyyy|}^T--TzTTn~~~}}}||||{yyyyxxz}_T--T|T{|Tu~~}}|||||yyyyyyuuy}]T--TVVWW||||||||yyyyyyttttyz^T--TfUT^||||||yyyyyyytttttty^T--TyTlkTm||||yyyyyyytttttttty^T--T_TS\||||yyyyyyttttttttrtt^T--TwzUVVTt{{yyyyyytttttttttqqqs^T--TwnUddTj}yzyyyyyttttttttqqqqqq^T--TwgTVVTf|zyyyyyttttttttqqqqqqqq^T--TwnTTllTTjyyyyyyutttttttqqqqqqqqqq^T--Tw~t]TTWWST\nyyyyyyttttttttqqqqqqqqqqq^T--Tw~~~~}n_TTUUTTSS]kyyyyyyttttttttqqqqqqqqqqqqq^T--Tw~~~~~~~~~xojegmotyyyyyyttttttttqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~|||||{{zyyyyyttttttttqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~~}|||||yyyyyyyttttttttqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~~||||||yyyyyyyttttttttqqqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~}||||||yyyyyyttttttttqqqqqqqqqqqqqqqqqqqqqq^T--Tw~~~~~~~~~|||||||yyyyyytttttttttqqqqqqqqqqqqqqqqqqqqqqq^T-.Uo~~~~~~~~~~|||||{yyyyyyyttttttttqqqqqqqqqqqqqqqqqqqqqqqqq]T-,Uj~~~~~~~~~~||||||zyyyyyyttttttttrqqqqqqqqqqqqqqqqqqqqqqqqqq]T-T\~~~~~~~~~}}|||||zzyyyyttttttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqq]TTT|||||zzyyyytttttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnTT>T\}}||yyyyttttqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnWT>JTWovwvvvvvvvvvvvvvwnnvnnnjnnnnjjfggggggggggggggggggggggggggggggcWTJBTUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTUC>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB>뀀")(("#")37883)$"37?FFE>72"efffffffff"*7BKRWRLB7)!\[[[[[[[[[""3?LWbcbXL?3#[MBBBBBBN[")7FSbouj_RB8)"[MBBBBBBN[+&#)6"$/7AIP\gd\\[[]_____________^^^^]]]]\\\\\\\\\\\\]UFB;6/(.:1)-))!!"'&"")*-3778@GPTU[oo[]bJAAAAAAAAAAAA@@@@@@@@@@@@@D@DDDDDDDDDDDDD>613.-**)))))*.3768BGGMTU[oo[^aJAAAAAAAAAAA@@@@@@@@@@@@D@DDDDDDDDDDDDDDD:=511*-------31678BGGGMTU[oo[^aJAAAAAAAAAA@@@@@@??????CDDDDDDDDDDDDDDDDDDB>661112222166=8BGGGHMTU[oo[^aJAAAAAAAA@@@@@@@?????C?DDDDDDDDDDDDDDDDDBBBB:==6666666<8BFGGGGGMTU[oo[^aJAAAAAAA@@@@@@@@@@@D@DDDDDDDDDDDDDDCDDDBBBBBBBB98=<=8BBHGGGGGFGMTU[oo[^aJAAAAA@A@@@@@@@@@@DDDDDDDDDDDDDDEDDDDBBBBBBBBBB?FAF@GGHGGGGGFFFMTU[oo[^^JAAA@A@@@@@@@@@@DDDDDDDDDDDDDDMMNKFBBBBBBBBBB??@GGGGGHGGGGFFFFFMTU[oo[^^JA@@@@@@@@@@@@D@DDDDDDDDDCGPZ[[[[[[XRJBBBBB@@HAHHHGGGGGGFFFFFFFLTU[oo[^^J@@@@@@@@@@@D@DDDDDDDDDDERZ[\fgjjgf[[\RDCB>H@GGHGGGGGGGFFFFFFCCLST[oo[^^J@@@@@@@@@@DDDDDDDDDDDDI\[\q||q\[\NG@GGGGGGGGGGFFFFFFCCAALST[oo[^^J@@@@@@@@DDDDDDDDDDCDDN[[n~n[[QFGGGGGGGGFFFFFFFCAAAALRT[oo[]^J@@@@@@D@DDDDDDDDDCDDH[[tt[[MGGGGGGFFFFFFFCAAAAAALRS[oo[]]J@@@@D@DDDDDDDDDDDDDF\[ut\]JGGGGFFFFFFCCAAAAAAALQS[oo[]]J@@@DDDDDDDDDDDDDDDDR\mm\SGGGEFFFFCCAAAAAAAAAKQSZoo[]]J@E@DDDDDDDDDDDDDDDK[^]\IFFGFFFCAAAAAAAAAAKKQSZoo[]^J@DDDDDDDDDDDDDDDDDS[qq[RGEFFCCAAAAAAAAJJJJJPSZoo[]\JDDDDDDDDDDDDDDDCDBW[~[]CFCCAAAAAAAAAAJJJJJOSZoo[\YJDDDDDDDDDDDDDDDBCK\ed[FCAAAAAAAAAAJJJJJJJMS[oo[\YJDDDDDDDDDDDDDBBBBG[kk\JAAAAAAAAAJJJJJJJIJLS[oo[[YJDDDDDDDDDDDDBBBBBM[nm[QAAAAAAAJJJJJJJHHHJKS[oo[[YJDDDDDDDDDDBBBBBBAM[om[QAAAAAAJJJJJJHHHHHJJS[oo[[YJDDDDDDDDBBBBBBBACN[kj[JABAAJJJJJJHHHHHHHHIS[oo[[YJDDDDDDDBBBBBBBBA?I[edZKBKJJJJJJJHHHHHHGGHIS[oo[[YIDDDCDBCBBBBBBBB@FH[\\WJIJJJJJJHHHHHHHGFFFHS[oo[[WIDDDBCBBBBBBBB@G@GGRZuq[TJJJJJJHHHHHHHGFFFFFHS[oo[[VIDCCBBBBBBBB@@@GFGGNZb~b[KJJIIHHHHHHHGFFFFFFGHS[oo[[UIBBBBBBBBB@@G@GGGGGGSZqqZYJJIIHHHHHHFFFFFFFGHHMS[oo[[UIBBBBBBBB@G@GGGGGGGGH[\vv\ZNIIHHHHHHFFFFFFFFGHHHGS[oo[ZUIBBCBAB@@@GGGGGGGGGGGJZ]ww][RJIHHHHHHFFFFFFFGHHHHHIS[op[ZVHBCBBA@?GGGGGGGGFFFFGFR[[qq[[RJHHHHHHFFFFFFFGHHHHHHHIS[op[ZVHBBBAF@GGGGGGGGGFFFFFGFR[[bttb[[QHIHHHHFFFFFFFFHHHHHHHHHIS[op[ZVHBAG@GGGGGGGGGGFFFFFFCCALZ[\\goqqog\[[YKHIHHHHFFFFFFFFHHHHHHHHHHIS[oo[ZUHAAGGGGGGGGGGFFFFFFCCAAAA@JS\\[[[[[[SPHHHHHHGFFFFFFFHHHHHHHHHHHHIS[oo[ZUHHGGGGGGGGGFFFFFFFCAAAAAAAA@JJRRRQIHHHHHHHFFFFFFFFHHHHHHHHHHHHHHIS[oo[ZUGGGGGGGGGFFFFFFFCBAAAAAAAAAJJJJJJIHHHHHHFFFFFFFGHHHHHHHHHHHHHHHHIS[oo[ZTGGGGGGGGFFFFFFBBAAAAAAAAAAJJJJJIIIHHHHHEFFFFFFGGHHHHHHHHHHHHHHHHIS[oo[ZRGGGGGGGFFFFFFBAAAAAAAAAAKJJJJIIIHHHHIEEFFFFFGGHHHHHHHHHHHHHHHHHHIS[oo[ZQGGGGGGFFFFFBAAAAAAAAAJJJJJJIIHHHHHIEEFFFFFGGHHHHHHHHHHHHHHHHHHHHIS[oo[ZQGGGGGGFFFBAAAAAAAAAAJJJJJJIIHHHHIEFFFFFFFGHHHHIHHHHHHHHHHHHHHHHHIS[op[VPFFFFFFFDAAAAAAAAAABKJJJIHHHHHHHHFFFFFFFFHHHHHHHHHHHHHHHHHHHHHHHHHS[pp[VPKGFFFFDAAAAAAAAAAJJJJJIIHHHHHHFFFFFFGGGGHHHHHHHHHHHHHHHHHHHHHHHHHW[p[YPMFFGCAAAAAAAAAKKJJJJJJIHHHHHFFFFFFFFHHHHHHHHHHHHHHHHHHHHHHHHHHHHHW[[[PQNLEFFGJAAKKKKKKKKJJJJIHHHHHFFFFFFFGHHHHHHHHHHHHHHHHHHHHHHHHHHHHH[[e[YQQQQQQLLLLLLKKKKKKKKKKIIIIHHHHFFFFHHHHHHHHHHHHHHHHHHHHHHHHHHHHHMW[f`[WRNSMLMSSMMMMMMSRRQQMQQQQQQQQQQQQPPPPPOOOOOOOOOOOOOOOOOOOOOOMMSW[ce[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[Z[[[[[[[[[[[[[[[[[[[[[[[[[[[[[cfcbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccfvwwwvvvvvvqqqqppppppqnddddddmppjddddddmpuuutvqpjjjjjmopttttttttttttttttttttttx}}}vpppppppppppppppppppppppppppppppppppppqw}}wpvtpqqqrrrrrrrrrrrrrrrrssssssrqpppppppppppqy}|vqpptvorqrrssssstttttttttssrrrrrrrrrrrrsssssporx~yvonnpvtptrojiggggggggggggggggggggggggggggggggggirq{{tplplptprsodddddddddddddddddheghhhhhhhhhggggggggglry|{vokklmopzprsidddddddddddddddddhhhhhhhhhhggggggggggggkrz}{vtqiemnopzzprsiddddddddddddeddhhhhhhhhhhghgggggggggggggmty{{vtqkfeklopzzprsiddddddddddfdhefhhhhhhhhhgggggggggggggggflotyy|{yttpkffgjlopzzprsiddddddddddhdfhhhhhhhhhgggggggggggggggggggjltuzyz{{{{zxtpqjffffklopzzprsiddddddddddfhhhhhhhhhggggggggggggggggggggggglopustttttppkefffffklopzzprtiddddddddghhhhhhhhhhggggggggggggggggggggggggggjlooooqkkeffffhhhllopzzprtidddddhdghhhhhhhhhgggggggggggggggggggggggggggggfgghgffffffgghiillopzzprsidddhdhhhhhhhhhhggggggggggggggjjjihgggggggggggggeeeeffffggigiihklopzzprsgdddhhhhhhhhhhggggggggggggimpppoopppmiggggggffffeeffffffiiiiiiiklopzzpqshddhhhhhhhhhhgggggggggggimppptvwwvtpppmigggfffffffffffghiiiiiiiklopzzpqrhhhhhhhhhhhggggggggggggjpprz~~zrpplfffffffffffghiiiiiiijjklopzzpqrhhhhhhhhhgggggggggggggjopyypomffffffffghiiiiiiijjjjjjopzzpprhhhhhhhggggggggggggggkppzzpojfffffggiiiiiiijjjjjjkjopzzppqhhhhhghgggggggggggggippzzqohffggiiiiiiiijjjjjjjkjopzzppphhhhggggggggggggggggppyxpogghhiiiiiijjjjjjjjjijopzzppphhghggggggggggggggggpssqlhhiiiiijjjjjjjjjjhhlopzzppphhggggggggggggggggglpyyplihhiiijjjjjjjjhhhhhlopzzppphggggggggggggggggggppppiiiiijjjjjjjjjhhhhllopzzppqhggggggggggggggghginttpjiijjjjjjjjjhhhhhhllopzzppqhggggggggggggggghgjowwpkjijjjjjjjhhhhhhhhllopzzppqhgggggggggggggggggjoxypljjjjjjjhhhhhhhhhhllopzzppqhgggggggggggggggggjpxypljjjjjjhhhhhhhhhhhhlopzzqpqhgggggggggggggggggjpvwpkjjjjhhhhhhhhhhhhhhlopzzqpphgggggggggggggggggiprtpjjihhhhhhhhhhhhhhihlopzzqpphggggggggggggggggfeqpoqihhhhhhhhhhhhhhiiiilopzzqpphggggggggggggggfffelp{{pohhhhhhhhhhhhhiiiiiikopzzqpphgggggggggggggfffffkqqspjhhhhhhhhhhhiiiiiiiikopzzqpphgggggggggggffeefffeqqy~wpohhhhhhhhhhiiiiiiiiihjopzzqpphgggggggggfgeeffffggiqp}}ppihhhhhhhhiiiiiiiiihhhjopzzqpphgggggggggeeefffffggijqr}}tpihhhhhhhhiiiiiiiihhhhhjopzzqpphgggggggeeeeffffefhihimppzzppkhhhhhhhiiiiiiiihhhhhhhiopzzqpphgggggggeeefffffiiiiiiimppr{{rppihhhhhhiiiiiiiihhhhhhhhhiopzzponiggfgfffffffffghiiiiiiijlnpppvvyyvvpppnlhhhhhhiiiiiiiihhhhhhhhhhiopzzpoliggfffffffffghhhiiiiijjjjjjoppppppppojhhhhhhiiiiiiiihhhhhhhhhhhhiopzzpolifffffffffghiiiiiiijjjjjjjjjikkkkkkhhhhhhhiiiiiiiihhhhhhhhhhhhhhiopzzqplifffffffghiiiiiiiijjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhiopzzqplifffffffiiiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiihhhhhhhhhhhhhhhhhhiopzzqplifffffifiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhiopzzpplifffiiiiiiiijjjjjjjjjihhhhhhhhhhhhhiiiiiiiiihhhhhhhhhhhhhhhhhhhhiopzzppliffiiiiiiijjjjjjjjjjhhhhhhhhhhhhhiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhiopzzpoliiiiiiiijjjjjjjjjjjhhhhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhiopzzpolkiiiiiiijjjjjjjjjjhhhhhhhhhhhhiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhiopzpolkjiiiiiijjjjjjihihhhhhhhhhhhiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhiopppmjkjiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhippupnjjiiiijkkkkklllllllllllllhlhhhiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhjopvspomnmnmmmmnnnnnnlllllmmnnimmmmiimljiiiiiiiiiiiiiiiiiiiiiiiiiijjopptupppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppptvuuttttttttttttttttttttttttttttttttttttttttttttttttttttttttttv \ No newline at end of file diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-wedge-compound-libavif.png b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-wedge-compound-libavif.png new file mode 100644 index 0000000000..c94b799dc6 --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-wedge-compound-libavif.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2cb388c9092ef17c4f0382c0150dd30d6f9d0ee247ff45ab5d7d4d312ceb23c +size 4544 diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-wedge-compound.avif b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-wedge-compound.avif new file mode 100644 index 0000000000..d48a785592 --- /dev/null +++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-wedge-compound.avif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98640640a445055fea3d9e2f4a78feef54e97f8171b472cf57d99156e1c553b2 +size 6439