diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md
index a34296e64..2b086a603 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 `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.
+Last reconciled with the source tree on 2026-08-29 against production checkpoint `864b11ad9791d3254a38691246270c569001b5a1`. 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, allocation-free SIMD-first equal averaging, selected inter-intra prediction, and selectable 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 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.
+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. Commit `864b11ad9791d3254a38691246270c569001b5a1` records the selectable-compound checkpoint. OBMC is current; warped motion, scaled references, and non-translational global prediction remain explicitly unsupported. Neither AV1 nor HEVC production encoding is implemented.
-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.
+Immediate checkpoint: **implement and independently verify OBMC before advancing to scaled references.** 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 the production OBMC 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, 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. |
+| 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 committed selectable compound and inter-intra reconstruction through reference-dependent bounded sequences. | 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. |
@@ -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 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.
+- 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. Commit `864b11ad9791d3254a38691246270c569001b5a1` records the selectable-compound checkpoint after 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` passed. 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.
- - [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.
+ - [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. Commit `864b11ad9791d3254a38691246270c569001b5a1` records the checkpoint.
- [ ] 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.
@@ -810,7 +810,7 @@ The dashboard and immediate execution queue define the remaining critical path.
- [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. 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.
+- [ ] **Current:** 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/Prediction/Inter/Av1ObmcMask.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1ObmcMask.cs
new file mode 100644
index 000000000..044b94ea1
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1ObmcMask.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter;
+
+///
+/// Provides the one-dimensional alpha masks used by AV1 overlapping motion compensation.
+///
+internal static class Av1ObmcMask
+{
+ ///
+ /// Gets the mask whose length matches one overlapping prediction axis.
+ ///
+ /// The power-of-two overlap length.
+ /// The alpha values applied to the regular block predictor.
+ public static ReadOnlySpan Get(int length) => length switch
+ {
+ 1 => [64],
+ 2 => [45, 64],
+ 4 => [39, 50, 59, 64],
+ 8 => [36, 42, 48, 53, 57, 61, 64, 64],
+ 16 => [34, 37, 40, 43, 46, 49, 52, 54, 56, 58, 60, 61, 64, 64, 64, 64],
+ 32 =>
+ [
+ 33, 35, 36, 38, 40, 41, 43, 44,
+ 45, 47, 48, 50, 51, 52, 53, 55,
+ 56, 57, 58, 59, 60, 60, 61, 62,
+ 64, 64, 64, 64, 64, 64, 64, 64
+ ],
+ 64 =>
+ [
+ 33, 34, 35, 35, 36, 37, 38, 39,
+ 40, 40, 41, 42, 43, 44, 44, 44,
+ 45, 46, 47, 47, 48, 49, 50, 51,
+ 51, 51, 52, 52, 53, 54, 55, 56,
+ 56, 56, 57, 57, 58, 58, 59, 60,
+ 60, 60, 60, 60, 61, 62, 62, 62,
+ 62, 62, 63, 63, 63, 63, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64
+ ],
+ _ => throw new ArgumentOutOfRangeException(nameof(length))
+ };
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
index 53b8f5126..be30f4216 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
@@ -2074,7 +2074,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
}
modeInfo.MotionMode = reader.ReadMotionMode(modeInfo.BlockSize, allowWarpedMotion);
- if (modeInfo.MotionMode != Av1MotionMode.SimpleTranslation)
+ if (modeInfo.MotionMode == Av1MotionMode.Warped)
{
throw new NotSupportedException($"AV1 {modeInfo.MotionMode} block prediction is not implemented.");
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs
index 3329b812f..0af7f4fef 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs
@@ -707,6 +707,23 @@ internal sealed class Av1BlockDecoder : IDisposable
predictionHeight);
}
}
+ else if (modeInfo.MotionMode == Av1MotionMode.Obmc)
+ {
+ this.ApplyOverlappedMotionCompensation(
+ ref partitionInfo,
+ plane,
+ subX,
+ subY,
+ predictionWidth,
+ predictionHeight,
+ blockReconstructionBuffer,
+ highBitDepthBlockReconstructionBuffer,
+ reconstructionStride,
+ secondPrediction,
+ highBitDepthSecondPrediction,
+ compoundMask,
+ predictionScratch);
+ }
}
for (int tu = 0; tu < transformUnitCount; tu++)
@@ -954,6 +971,301 @@ internal sealed class Av1BlockDecoder : IDisposable
return referenceFrameBuffer;
}
+ ///
+ /// Blends predictions from eligible above and left neighbors into one regular inter prediction.
+ ///
+ private void ApplyOverlappedMotionCompensation(
+ ref Av1PartitionInfo partitionInfo,
+ int plane,
+ int subX,
+ int subY,
+ int predictionWidth,
+ int predictionHeight,
+ Span blockReconstructionBuffer,
+ Span highBitDepthBlockReconstructionBuffer,
+ int reconstructionStride,
+ Span neighborPrediction,
+ Span highBitDepthNeighborPrediction,
+ Span maskStorage,
+ Span predictionScratch)
+ {
+ Av1BlockSize blockSize = partitionInfo.ModeInfo.BlockSize;
+ int blockWidthInModeInfoUnits = blockSize.Get4x4WideCount();
+ int blockHeightInModeInfoUnits = blockSize.Get4x4HighCount();
+ int blockColumn = partitionInfo.ColumnIndex;
+ int blockRow = partitionInfo.RowIndex;
+ bool highBitDepth = this.frameBuffer.BytesPerSample == 2;
+
+ // Chroma planes smaller than 8x8 use left overlap only. This is the AV1 bandwidth rule for 4x4,
+ // 8x4, and 4x8 plane blocks; luma cannot reach those sizes when motion variation is selectable.
+ bool skipAbove = (predictionWidth == 4 && predictionHeight <= 8) ||
+ (predictionWidth == 8 && predictionHeight == 4);
+
+ if (partitionInfo.AvailableAbove && !skipAbove)
+ {
+ int maximumNeighbors = Math.Min(4, blockSize.Get4x4WidthLog2());
+ int endColumn = Math.Min(blockColumn + blockWidthInModeInfoUnits, this.frameHeader.ModeInfoColumnCount);
+ int neighborCount = 0;
+ for (int aboveColumn = blockColumn; aboveColumn < endColumn && neighborCount < maximumNeighbors;)
+ {
+ Av1BlockModeInfo candidate = partitionInfo.SuperblockInfo.GetModeInfoAt(new Point(aboveColumn, blockRow - 1));
+ int step = Math.Min(candidate.BlockSize.Get4x4WideCount(), Av1BlockSize.Block64x64.Get4x4WideCount());
+ if (step == 1)
+ {
+ // A four-sample neighbor is one half of the chroma-bearing eight-sample pair. libaom aligns
+ // the traversal to the pair start and reads prediction state from its second mode record.
+ aboveColumn &= ~1;
+ candidate = partitionInfo.SuperblockInfo.GetModeInfoAt(new Point(aboveColumn + 1, blockRow - 1));
+ step = 2;
+ }
+
+ if (IsOverlappable(candidate))
+ {
+ int relativeColumn = aboveColumn - blockColumn;
+ int neighborWidthInModeInfoUnits = Math.Min(blockWidthInModeInfoUnits, step);
+ int neighborWidth = (neighborWidthInModeInfoUnits << Av1Constants.ModeInfoSizeLog2) >> subX;
+ int neighborHeight = Math.Clamp(
+ blockSize.GetHeight() >> (subY + 1),
+ 4,
+ Av1BlockSize.Block64x64.GetHeight() >> (subY + 1));
+
+ Point predictionOrigin = new(
+ (aboveColumn << Av1Constants.ModeInfoSizeLog2) >> subX,
+ (blockRow << Av1Constants.ModeInfoSizeLog2) >> subY);
+
+ this.PredictObmcNeighbor(
+ candidate,
+ plane,
+ subX,
+ subY,
+ predictionOrigin,
+ neighborWidth,
+ neighborHeight,
+ neighborPrediction,
+ highBitDepthNeighborPrediction,
+ predictionScratch);
+
+ int overlapHeight = (Math.Min(blockSize.GetHeight(), Av1BlockSize.Block64x64.GetHeight()) >> 1) >> subY;
+ int destinationColumn = (relativeColumn << Av1Constants.ModeInfoSizeLog2) >> subX;
+ ReadOnlySpan verticalMask = Av1ObmcMask.Get(overlapHeight);
+ Span expandedMask = maskStorage[..(neighborWidth * overlapHeight)];
+ for (int row = 0; row < overlapHeight; row++)
+ {
+ // The vertical mask has one alpha per row. Expanding it into the reusable scratch plane lets
+ // the existing SIMD masked blender process complete rows without a specialized duplicate path.
+ expandedMask.Slice(row * neighborWidth, neighborWidth).Fill(verticalMask[row]);
+ }
+
+ if (highBitDepth)
+ {
+ Av1CompoundInterPredictor.Blend(
+ MemoryMarshal.Cast(highBitDepthBlockReconstructionBuffer[reconstructionStride..])[destinationColumn..],
+ reconstructionStride,
+ highBitDepthNeighborPrediction,
+ neighborWidth,
+ expandedMask,
+ neighborWidth,
+ neighborWidth,
+ overlapHeight);
+ }
+ else
+ {
+ Av1CompoundInterPredictor.Blend(
+ blockReconstructionBuffer[reconstructionStride..][destinationColumn..],
+ reconstructionStride,
+ neighborPrediction,
+ neighborWidth,
+ expandedMask,
+ neighborWidth,
+ neighborWidth,
+ overlapHeight);
+ }
+
+ neighborCount++;
+ }
+
+ aboveColumn += step;
+ }
+ }
+
+ if (partitionInfo.AvailableLeft)
+ {
+ int maximumNeighbors = Math.Min(4, blockSize.Get4x4HeightLog2());
+ int endRow = Math.Min(blockRow + blockHeightInModeInfoUnits, this.frameHeader.ModeInfoRowCount);
+ int neighborCount = 0;
+ for (int leftRow = blockRow; leftRow < endRow && neighborCount < maximumNeighbors;)
+ {
+ Av1BlockModeInfo candidate = partitionInfo.SuperblockInfo.GetModeInfoAt(new Point(blockColumn - 1, leftRow));
+ int step = Math.Min(candidate.BlockSize.Get4x4HighCount(), Av1BlockSize.Block64x64.Get4x4HighCount());
+ if (step == 1)
+ {
+ // The vertical traversal applies the corresponding pairing rule to four-sample-high blocks.
+ leftRow &= ~1;
+ candidate = partitionInfo.SuperblockInfo.GetModeInfoAt(new Point(blockColumn - 1, leftRow + 1));
+ step = 2;
+ }
+
+ if (IsOverlappable(candidate))
+ {
+ int relativeRow = leftRow - blockRow;
+ int neighborHeightInModeInfoUnits = Math.Min(blockHeightInModeInfoUnits, step);
+ int neighborWidth = Math.Clamp(
+ blockSize.GetWidth() >> (subX + 1),
+ 4,
+ Av1BlockSize.Block64x64.GetWidth() >> (subX + 1));
+
+ int neighborHeight = (neighborHeightInModeInfoUnits << Av1Constants.ModeInfoSizeLog2) >> subY;
+ Point predictionOrigin = new(
+ (blockColumn << Av1Constants.ModeInfoSizeLog2) >> subX,
+ (leftRow << Av1Constants.ModeInfoSizeLog2) >> subY);
+
+ this.PredictObmcNeighbor(
+ candidate,
+ plane,
+ subX,
+ subY,
+ predictionOrigin,
+ neighborWidth,
+ neighborHeight,
+ neighborPrediction,
+ highBitDepthNeighborPrediction,
+ predictionScratch);
+
+ int overlapWidth = (Math.Min(blockSize.GetWidth(), Av1BlockSize.Block64x64.GetWidth()) >> 1) >> subX;
+ int destinationRow = (relativeRow << Av1Constants.ModeInfoSizeLog2) >> subY;
+ ReadOnlySpan horizontalMask = Av1ObmcMask.Get(overlapWidth);
+ if (highBitDepth)
+ {
+ Av1CompoundInterPredictor.Blend(
+ MemoryMarshal.Cast(highBitDepthBlockReconstructionBuffer[reconstructionStride..])[(destinationRow * reconstructionStride)..],
+ reconstructionStride,
+ highBitDepthNeighborPrediction,
+ neighborWidth,
+ horizontalMask,
+ 0,
+ overlapWidth,
+ neighborHeight);
+ }
+ else
+ {
+ Av1CompoundInterPredictor.Blend(
+ blockReconstructionBuffer[reconstructionStride..][(destinationRow * reconstructionStride)..],
+ reconstructionStride,
+ neighborPrediction,
+ neighborWidth,
+ horizontalMask,
+ 0,
+ overlapWidth,
+ neighborHeight);
+ }
+
+ neighborCount++;
+ }
+
+ leftRow += step;
+ }
+ }
+ }
+
+ ///
+ /// Builds one neighboring block's primary translational predictor into the reusable OBMC workspace.
+ ///
+ private void PredictObmcNeighbor(
+ Av1BlockModeInfo neighbor,
+ int plane,
+ int subX,
+ int subY,
+ Point predictionOrigin,
+ int predictionWidth,
+ int predictionHeight,
+ Span destination,
+ Span highBitDepthDestination,
+ Span predictionScratch)
+ {
+ Av1FrameBuffer referenceFrameBuffer = this.ResolveReferenceFrame(neighbor.ReferenceFrames[0]);
+ Av1MotionVector motionVector = neighbor.MotionVectors[0];
+ int sourceColumnQ4 = (predictionOrigin.X << 4) + (motionVector.Column << (1 - subX));
+ int sourceRowQ4 = (predictionOrigin.Y << 4) + (motionVector.Row << (1 - subY));
+ int horizontalExtensionQ4 = (4 + predictionWidth) << 4;
+ int verticalExtensionQ4 = (4 + predictionHeight) << 4;
+ int framePlaneWidth = (this.frameHeader.ModeInfoColumnCount << Av1Constants.ModeInfoSizeLog2) >> subX;
+ int framePlaneHeight = (this.frameHeader.ModeInfoRowCount << Av1Constants.ModeInfoSizeLog2) >> subY;
+
+ // OBMC changes the prediction rectangle but not AV1's unrestricted-motion-vector boundary extension.
+ // Clamping the absolute source coordinate expresses the same edge calculation libaom rebuilds per neighbor.
+ sourceColumnQ4 = Av1Math.Clip3(
+ -horizontalExtensionQ4,
+ (framePlaneWidth << 4) + horizontalExtensionQ4 - 16,
+ sourceColumnQ4);
+
+ sourceRowQ4 = Av1Math.Clip3(
+ -verticalExtensionQ4,
+ (framePlaneHeight << 4) + verticalExtensionQ4 - 16,
+ sourceRowQ4);
+
+ int horizontalPhase = sourceColumnQ4 & 15;
+ int verticalPhase = sourceRowQ4 & 15;
+ if (this.frameBuffer.BytesPerSample == 2)
+ {
+ Span source = referenceFrameBuffer.GetPaddedPlaneSpan16(
+ (Av1Plane)plane,
+ subX,
+ subY,
+ out int sourceStride,
+ out Point sourceOrigin);
+
+ int sourceIndex =
+ ((sourceOrigin.Y + (sourceRowQ4 >> 4)) * sourceStride) + sourceOrigin.X + (sourceColumnQ4 >> 4);
+
+ Av1InterPredictor.Predict(
+ source,
+ sourceStride,
+ sourceIndex,
+ highBitDepthDestination,
+ predictionWidth,
+ predictionWidth,
+ predictionHeight,
+ neighbor.InterpolationFilters[1],
+ neighbor.InterpolationFilters[0],
+ horizontalPhase,
+ verticalPhase,
+ this.frameBuffer.BitDepth.GetBitCount(),
+ predictionScratch);
+ }
+ else
+ {
+ Span source = referenceFrameBuffer.GetPaddedPlaneSpan(
+ (Av1Plane)plane,
+ subX,
+ subY,
+ out int sourceStride,
+ out Point sourceOrigin);
+
+ int sourceIndex =
+ ((sourceOrigin.Y + (sourceRowQ4 >> 4)) * sourceStride) + sourceOrigin.X + (sourceColumnQ4 >> 4);
+
+ Av1InterPredictor.Predict(
+ source,
+ sourceStride,
+ sourceIndex,
+ destination,
+ predictionWidth,
+ predictionWidth,
+ predictionHeight,
+ neighbor.InterpolationFilters[1],
+ neighbor.InterpolationFilters[0],
+ horizontalPhase,
+ verticalPhase,
+ predictionScratch);
+ }
+ }
+
+ ///
+ /// Determines whether a decoded neighbor supplies an inter predictor for OBMC.
+ ///
+ private static bool IsOverlappable(Av1BlockModeInfo candidate)
+ => candidate.UseIntraBlockCopy || candidate.ReferenceFrames[0] > Av1ReferenceFrameType.Intra;
+
///
/// Derives a byte-addressed reconstruction span beginning one row before a block.
///
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs
index c18d87dd5..dde92ff76 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs
@@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Motion;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter;
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantizers;
@@ -318,6 +319,272 @@ public class Av1CompoundBlockDecoderTests
}
}
+ ///
+ /// Verifies above-then-left OBMC 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 DecodeBlockReconstructsOverlappedMotionCompensation(int bitDepthValue)
+ {
+ const int frameSize = 24;
+ const int blockOrigin = 8;
+ Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue;
+ ObuSequenceHeader sequenceHeader = CreateSequenceHeader(bitDepth, frameSize);
+ ObuFrameHeader frameHeader = CreateFrameHeader(frameSize);
+ frameHeader.GetReferenceFrameIndices()[0] = 0;
+
+ using Av1ReferenceFrameStore referenceFrames = new();
+ Assert.True(referenceFrames.Commit(1, CreatePatternReferenceFrame(sequenceHeader, frameHeader), 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 above = CreateSingleReferenceModeInfo(Av1BlockSize.Block8x8, new Point(2, 0));
+ above.MotionVectors[0] = new Av1MotionVector(0, 8);
+ frameInfo.UpdateModeInfo(above, superblockInfo);
+
+ Av1BlockModeInfo left = CreateSingleReferenceModeInfo(Av1BlockSize.Block8x8, new Point(0, 2));
+ left.MotionVectors[0] = new Av1MotionVector(8, 0);
+ frameInfo.UpdateModeInfo(left, superblockInfo);
+
+ Av1BlockModeInfo current = CreateSingleReferenceModeInfo(Av1BlockSize.Block8x8, new Point(2, 2));
+ current.MotionMode = Av1MotionMode.Obmc;
+ current.SetTransformUnitCount(Av1PlaneType.Y, 1);
+ frameInfo.UpdateModeInfo(current, superblockInfo);
+
+ Av1LoopFilterContext loopFilterContext = new(sequenceHeader);
+ Av1InverseQuantizer inverseQuantizer = new(sequenceHeader, frameHeader);
+ using Av1BlockDecoder decoder = new(
+ sequenceHeader,
+ frameHeader,
+ frameBuffer,
+ loopFilterContext,
+ inverseQuantizer,
+ referenceFrames);
+
+ decoder.UpdateSuperblock(superblockInfo);
+ decoder.DecodeBlock(
+ current,
+ new Point(2, 2),
+ Av1BlockSize.Block8x8,
+ superblockInfo,
+ new Av1TileInfo(0, 0, frameHeader));
+
+ ReadOnlySpan mask = [39, 50, 59, 64];
+ for (int row = 0; row < 8; row++)
+ {
+ for (int column = 0; column < 8; column++)
+ {
+ int first = GetPatternValue(blockOrigin + column, blockOrigin + row);
+ if (row < mask.Length)
+ {
+ int aboveValue = GetPatternValue(blockOrigin + column + 1, blockOrigin + row);
+ first = ((mask[row] * first) + ((64 - mask[row]) * aboveValue) + 32) >> 6;
+ }
+
+ if (column < mask.Length)
+ {
+ int leftValue = GetPatternValue(blockOrigin + column, blockOrigin + row + 1);
+ first = ((mask[column] * first) + ((64 - mask[column]) * leftValue) + 32) >> 6;
+ }
+
+ if (bitDepth == Av1BitDepth.EightBit)
+ {
+ Span samples = frameBuffer.DeriveBlockPointer(Av1Plane.Y, 0, 0).DangerousGetRowSpan(blockOrigin + row);
+ Assert.Equal((byte)first, samples[blockOrigin + column]);
+ }
+ else
+ {
+ Span samples = frameBuffer.GetHighBitDepthRowSpan(Av1Plane.Y, blockOrigin + row, 0, 0);
+ Assert.Equal((ushort)first, samples[blockOrigin + column]);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Verifies the above and left OBMC rectangles on horizontally and vertically subsampled chroma planes.
+ ///
+ /// The chroma-subsampling layout to reconstruct.
+ [Theory]
+ [InlineData((int)Av1ColorFormat.Yuv420)]
+ [InlineData((int)Av1ColorFormat.Yuv422)]
+ public void DecodeBlockReconstructsSubsampledOverlappedMotionCompensation(int colorFormatValue)
+ {
+ const int frameSize = 48;
+ const int blockOrigin = 16;
+ Av1ColorFormat colorFormat = (Av1ColorFormat)colorFormatValue;
+ ObuSequenceHeader sequenceHeader = CreateSequenceHeader(Av1BitDepth.EightBit, frameSize, colorFormat);
+ ObuFrameHeader frameHeader = CreateFrameHeader(frameSize);
+ frameHeader.GetReferenceFrameIndices()[0] = 0;
+
+ using Av1ReferenceFrameStore referenceFrames = new();
+ Assert.True(referenceFrames.Commit(1, CreatePatternReferenceFrame(sequenceHeader, frameHeader, colorFormat), showFrame: false));
+
+ using Av1FrameBuffer frameBuffer = new(
+ Configuration.Default,
+ sequenceHeader,
+ colorFormat,
+ false);
+
+ using Av1FrameInfo frameInfo = new(sequenceHeader);
+ Av1SuperblockInfo superblockInfo = frameInfo.GetSuperblock(Point.Empty);
+ superblockInfo.GetTransformInfoY()[0] = new Av1TransformInfo(Av1TransformSize.Size16x16, 0, 0);
+ superblockInfo.GetTransformInfoUv()[0] = new Av1TransformInfo(Av1TransformSize.Size8x8, 0, 0);
+ superblockInfo.GetTransformInfoUv()[1] = new Av1TransformInfo(Av1TransformSize.Size8x8, 0, 0);
+
+ Av1BlockModeInfo above = CreateSingleReferenceModeInfo(Av1BlockSize.Block16x16, new Point(4, 0));
+ above.MotionVectors[0] = new Av1MotionVector(0, 16);
+ frameInfo.UpdateModeInfo(above, superblockInfo);
+
+ Av1BlockModeInfo left = CreateSingleReferenceModeInfo(Av1BlockSize.Block16x16, new Point(0, 4));
+ left.MotionVectors[0] = new Av1MotionVector(16, 0);
+ frameInfo.UpdateModeInfo(left, superblockInfo);
+
+ Av1BlockModeInfo current = CreateSingleReferenceModeInfo(Av1BlockSize.Block16x16, new Point(4, 4));
+ current.MotionMode = Av1MotionMode.Obmc;
+ current.SetTransformUnitCount(Av1PlaneType.Y, 1);
+ current.SetTransformUnitCount(Av1PlaneType.Uv, 1);
+ frameInfo.UpdateModeInfo(current, superblockInfo);
+
+ Av1LoopFilterContext loopFilterContext = new(sequenceHeader);
+ Av1InverseQuantizer inverseQuantizer = new(sequenceHeader, frameHeader);
+ using Av1BlockDecoder decoder = new(
+ sequenceHeader,
+ frameHeader,
+ frameBuffer,
+ loopFilterContext,
+ inverseQuantizer,
+ referenceFrames);
+
+ decoder.UpdateSuperblock(superblockInfo);
+ decoder.DecodeBlock(
+ current,
+ new Point(4, 4),
+ Av1BlockSize.Block16x16,
+ superblockInfo,
+ new Av1TileInfo(0, 0, frameHeader));
+
+ ReadOnlySpan mask4 = [39, 50, 59, 64];
+
+ ReadOnlySpan mask8 = [36, 42, 48, 53, 57, 61, 64, 64];
+
+ for (int plane = (int)Av1Plane.U; plane <= (int)Av1Plane.V; plane++)
+ {
+ int subY = colorFormat == Av1ColorFormat.Yuv420 ? 1 : 0;
+ int planeOriginX = blockOrigin >> 1;
+ int planeOriginY = blockOrigin >> subY;
+ int predictionWidth = Av1BlockSize.Block16x16.GetWidth() >> 1;
+ int predictionHeight = Av1BlockSize.Block16x16.GetHeight() >> subY;
+ int leftSourceRowOffset = 2 >> subY;
+ ReadOnlySpan verticalMask = subY == 0 ? mask8 : mask4;
+
+ for (int row = 0; row < predictionHeight; row++)
+ {
+ Span samples = frameBuffer.DeriveBlockPointer((Av1Plane)plane, 1, subY).DangerousGetRowSpan(planeOriginY + row);
+ for (int column = 0; column < predictionWidth; column++)
+ {
+ int expected = GetPlanePatternValue(plane, planeOriginX + column, planeOriginY + row);
+ if (row < verticalMask.Length)
+ {
+ int aboveValue = GetPlanePatternValue(plane, planeOriginX + column + 1, planeOriginY + row);
+ expected = ((verticalMask[row] * expected) + ((64 - verticalMask[row]) * aboveValue) + 32) >> 6;
+ }
+
+ if (column < mask4.Length)
+ {
+ int leftValue = GetPlanePatternValue(plane, planeOriginX + column, planeOriginY + row + leftSourceRowOffset);
+ expected = ((mask4[column] * expected) + ((64 - mask4[column]) * leftValue) + 32) >> 6;
+ }
+
+ Assert.Equal((byte)expected, samples[planeOriginX + column]);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Creates one skipped single-reference mode record for direct block-reconstruction tests.
+ ///
+ private static Av1BlockModeInfo CreateSingleReferenceModeInfo(Av1BlockSize blockSize, Point position)
+ {
+ Av1BlockModeInfo modeInfo = new(blockSize, position)
+ {
+ Skip = true,
+ YMode = Av1PredictionMode.NearestMotionVector,
+ };
+
+ modeInfo.ReferenceFrames[0] = Av1ReferenceFrameType.Last;
+ modeInfo.ReferenceFrames[1] = Av1ReferenceFrameType.None;
+ modeInfo.InterpolationFilters.Clear();
+ return modeInfo;
+ }
+
+ ///
+ /// Creates one retained frame whose integer-coordinate luma samples make both OBMC axes observable.
+ ///
+ private static Av1ReferenceFrame CreatePatternReferenceFrame(
+ ObuSequenceHeader sequenceHeader,
+ ObuFrameHeader frameHeader,
+ Av1ColorFormat colorFormat = Av1ColorFormat.Yuv400)
+ {
+ Av1FrameBuffer frameBuffer = new(
+ Configuration.Default,
+ sequenceHeader,
+ colorFormat,
+ false);
+
+ for (int plane = 0; plane < sequenceHeader.ColorConfig.PlaneCount; plane++)
+ {
+ int subX = plane > 0 && sequenceHeader.ColorConfig.SubSamplingX ? 1 : 0;
+ int subY = plane > 0 && sequenceHeader.ColorConfig.SubSamplingY ? 1 : 0;
+ int planeWidth = sequenceHeader.MaxFrameWidth >> subX;
+ int planeHeight = sequenceHeader.MaxFrameHeight >> subY;
+ for (int row = 0; row < planeHeight; row++)
+ {
+ if (sequenceHeader.ColorConfig.BitDepth == Av1BitDepth.EightBit)
+ {
+ Span samples = frameBuffer.DeriveBlockPointer((Av1Plane)plane, subX, subY).DangerousGetRowSpan(row);
+ for (int column = 0; column < planeWidth; column++)
+ {
+ samples[column] = (byte)GetPlanePatternValue(plane, column, row);
+ }
+ }
+ else
+ {
+ Span samples = frameBuffer.GetHighBitDepthRowSpan((Av1Plane)plane, row, subX, subY);
+ for (int column = 0; column < planeWidth; column++)
+ {
+ samples[column] = (ushort)GetPlanePatternValue(plane, column, row);
+ }
+ }
+ }
+ }
+
+ using Av1FrameInfo frameInfo = new(sequenceHeader);
+ return new Av1ReferenceFrame(frameBuffer, frameHeader, frameInfo);
+ }
+
+ ///
+ /// Gets the deterministic luma value stored at one reference-frame coordinate.
+ ///
+ private static int GetPatternValue(int column, int row) => column + (row * 4);
+
+ ///
+ /// Gets the deterministic plane value stored at one reference-frame coordinate.
+ ///
+ private static int GetPlanePatternValue(int plane, int column, int row) => GetPatternValue(column, row) + (plane * 20);
+
///
/// Creates one independently owned retained frame filled with a constant visible luma value.
///
@@ -351,33 +618,38 @@ public class Av1CompoundBlockDecoderTests
///
/// Creates the monochrome 8x8 sequence used by direct reconstruction tests.
///
- private static ObuSequenceHeader CreateSequenceHeader(Av1BitDepth bitDepth)
+ private static ObuSequenceHeader CreateSequenceHeader(
+ Av1BitDepth bitDepth,
+ int frameSize = 8,
+ Av1ColorFormat colorFormat = Av1ColorFormat.Yuv400)
=> new()
{
- MaxFrameWidth = 8,
- MaxFrameHeight = 8,
+ MaxFrameWidth = frameSize,
+ MaxFrameHeight = frameSize,
Use128x128Superblock = false,
ColorConfig = new ObuColorConfig
{
- IsMonochrome = true,
+ IsMonochrome = colorFormat == Av1ColorFormat.Yuv400,
BitDepth = bitDepth,
+ SubSamplingX = colorFormat is Av1ColorFormat.Yuv420 or Av1ColorFormat.Yuv422,
+ SubSamplingY = colorFormat == Av1ColorFormat.Yuv420,
},
};
///
/// Creates an unscaled 8x8 inter-frame header with one complete tile.
///
- private static ObuFrameHeader CreateFrameHeader()
+ private static ObuFrameHeader CreateFrameHeader(int frameSize = 8)
{
ObuFrameHeader frameHeader = new()
{
FrameType = ObuFrameType.InterFrame,
- ModeInfoColumnCount = 2,
- ModeInfoRowCount = 2,
+ ModeInfoColumnCount = frameSize >> Av1Constants.ModeInfoSizeLog2,
+ ModeInfoRowCount = frameSize >> Av1Constants.ModeInfoSizeLog2,
FrameSize = new ObuFrameSize
{
- FrameWidth = 8,
- FrameHeight = 8,
+ FrameWidth = frameSize,
+ FrameHeight = frameSize,
},
};
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeInfoTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeInfoTests.cs
index dd818ecd2..b14ab0533 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeInfoTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeInfoTests.cs
@@ -54,17 +54,21 @@ public class Av1MotionModeInfoTests
}
///
- /// Verifies that a false inter-intra flag continues through omitted, binary, and ternary Simple Translation syntax into interpolation.
+ /// Verifies that a false inter-intra flag continues through omitted, binary, and ternary motion-mode syntax into interpolation.
///
/// Whether the frame enables per-block motion-mode syntax.
/// Whether the eligible block uses the ternary rather than binary motion-mode distribution.
+ /// The motion mode written when syntax is present.
[Theory]
- [InlineData(false, false)]
- [InlineData(true, false)]
- [InlineData(true, true)]
+ [InlineData(false, false, (int)Av1MotionMode.SimpleTranslation)]
+ [InlineData(true, false, (int)Av1MotionMode.SimpleTranslation)]
+ [InlineData(true, true, (int)Av1MotionMode.SimpleTranslation)]
+ [InlineData(true, false, (int)Av1MotionMode.Obmc)]
+ [InlineData(true, true, (int)Av1MotionMode.Obmc)]
public void ReadInterFrameModeInfoContinuesFromFalseInterIntraThroughMotionModeIntoInterpolation(
bool isMotionModeSwitchable,
- bool allowWarpedMotion)
+ bool allowWarpedMotion,
+ int selectedMotionModeValue)
{
ObuSequenceHeader sequenceHeader = CreateSequenceHeader();
sequenceHeader.EnableInterIntraCompound = true;
@@ -122,7 +126,7 @@ public class Av1MotionModeInfoTests
? Av1DefaultDistributions.MotionMode[(int)Av1BlockSize.Block8x8]
: Av1DefaultDistributions.Obmc[(int)Av1BlockSize.Block8x8];
- writer.WriteSymbol((int)Av1MotionMode.SimpleTranslation, motionModeDistribution);
+ writer.WriteSymbol(selectedMotionModeValue, motionModeDistribution);
}
// The matching regular above neighbor selects vertical context zero. Sharp is deliberately non-default so the
@@ -136,7 +140,11 @@ public class Av1MotionModeInfoTests
Assert.Equal(Av1ReferenceFrameType.Last, modeInfo.ReferenceFrames[0]);
Assert.Equal(Av1ReferenceFrameType.None, modeInfo.ReferenceFrames[1]);
- Assert.Equal(Av1MotionMode.SimpleTranslation, modeInfo.MotionMode);
+ Av1MotionMode expectedMotionMode = isMotionModeSwitchable
+ ? (Av1MotionMode)selectedMotionModeValue
+ : Av1MotionMode.SimpleTranslation;
+
+ Assert.Equal(expectedMotionMode, modeInfo.MotionMode);
Assert.Equal(Av1InterpolationFilter.Sharp, modeInfo.InterpolationFilters[0]);
Assert.Equal(Av1InterpolationFilter.Sharp, modeInfo.InterpolationFilters[1]);
}
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
index a05446ae3..191f313b9 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
@@ -173,6 +173,11 @@ public class Av1ReconstructionConformanceTests
///
private const int WedgeInterIntraCoverage = 1 << 6;
+ ///
+ /// The coverage bit representing overlapping motion compensation.
+ ///
+ private const int ObmcCoverage = 1 << 7;
+
///
/// The hardware configurations covering the available vector widths and the scalar color-conversion fallback.
///
@@ -650,25 +655,25 @@ public class Av1ReconstructionConformanceTests
[ValidateDisposedMemoryAllocations]
public void DecodeRealLibavifSequencesWithSelectableCompoundAndInterIntraUseContiguousPlanes()
{
- ValidateSelectableCompoundSequenceWithConstrainedAllocator(
+ ValidateInterPredictionSequenceWithConstrainedAllocator(
TestImages.Heif.Av1DistanceWeightedCompoundSequenceAvif,
TestImages.Heif.Av1DistanceWeightedCompoundSequenceNativeReference,
TestImages.Heif.Av1DistanceWeightedCompoundSequencePresentationReference,
DistanceWeightedCompoundCoverage);
- ValidateSelectableCompoundSequenceWithConstrainedAllocator(
+ ValidateInterPredictionSequenceWithConstrainedAllocator(
TestImages.Heif.Av1WedgeCompoundSequenceAvif,
TestImages.Heif.Av1WedgeCompoundSequenceNativeReference,
TestImages.Heif.Av1WedgeCompoundSequencePresentationReference,
WedgeCompoundCoverage | InvertedWedgeCompoundCoverage);
- ValidateSelectableCompoundSequenceWithConstrainedAllocator(
+ ValidateInterPredictionSequenceWithConstrainedAllocator(
TestImages.Heif.Av1DifferenceWeightedCompoundSequenceAvif,
TestImages.Heif.Av1DifferenceWeightedCompoundSequenceNativeReference,
TestImages.Heif.Av1DifferenceWeightedCompoundSequencePresentationReference,
DifferenceWeightedCompoundCoverage | InvertedDifferenceWeightedCompoundCoverage);
- ValidateSelectableCompoundSequenceWithConstrainedAllocator(
+ ValidateInterPredictionSequenceWithConstrainedAllocator(
TestImages.Heif.Av1InterIntraSequenceAvif,
TestImages.Heif.Av1InterIntraSequenceNativeReference,
TestImages.Heif.Av1InterIntraSequencePresentationReference,
@@ -676,9 +681,30 @@ public class Av1ReconstructionConformanceTests
}
///
- /// Verifies one complete selectable-compound sequence with a separately tracked constrained allocator.
+ /// Verifies production OBMC reconstruction against pinned native and presentation references.
+ ///
+ [Fact]
+ public void DecodeRealLibavifObmcSequenceMatchesPinnedReferences()
+ => FeatureTestRunner.RunWithHwIntrinsicsFeature(
+ ValidateObmcSequenceWithDefaultConfiguration,
+ ReconstructionConfigurations);
+
+ ///
+ /// Verifies production OBMC reconstruction through a constrained allocator.
+ ///
+ [Fact]
+ [ValidateDisposedMemoryAllocations]
+ public void DecodeRealLibavifObmcSequenceUsesContiguousPlanes()
+ => ValidateInterPredictionSequenceWithConstrainedAllocator(
+ TestImages.Heif.Av1ObmcSequenceAvif,
+ TestImages.Heif.Av1ObmcSequenceNativeReference,
+ TestImages.Heif.Av1ObmcSequencePresentationReference,
+ ObmcCoverage);
+
+ ///
+ /// Verifies one complete inter-prediction sequence with a separately tracked constrained allocator.
///
- private static void ValidateSelectableCompoundSequenceWithConstrainedAllocator(
+ private static void ValidateInterPredictionSequenceWithConstrainedAllocator(
string imagePath,
string nativeReferencePath,
string presentationReferencePath,
@@ -689,7 +715,7 @@ public class Av1ReconstructionConformanceTests
Configuration configuration = Configuration.Default.Clone();
configuration.MemoryAllocator = allocator;
- ValidateSelectableCompoundSequence(
+ ValidateInterPredictionSequence(
configuration,
imagePath,
nativeReferencePath,
@@ -713,6 +739,18 @@ public class Av1ReconstructionConformanceTests
private static void ValidateSelectableCompoundSequencesWithDefaultConfiguration()
=> ValidateSelectableCompoundSequences(Configuration.Default, comparePresentation: true);
+ ///
+ /// Runs the OBMC sequence with exact final presentation comparison.
+ ///
+ private static void ValidateObmcSequenceWithDefaultConfiguration()
+ => ValidateInterPredictionSequence(
+ Configuration.Default,
+ TestImages.Heif.Av1ObmcSequenceAvif,
+ TestImages.Heif.Av1ObmcSequenceNativeReference,
+ TestImages.Heif.Av1ObmcSequencePresentationReference,
+ ObmcCoverage,
+ comparePresentation: true);
+
///
/// Validates every selectable compound fixture with the requested decoder configuration.
///
@@ -720,7 +758,7 @@ public class Av1ReconstructionConformanceTests
/// Whether to compare the final presented frame.
private static void ValidateSelectableCompoundSequences(Configuration configuration, bool comparePresentation)
{
- ValidateSelectableCompoundSequence(
+ ValidateInterPredictionSequence(
configuration,
TestImages.Heif.Av1DistanceWeightedCompoundSequenceAvif,
TestImages.Heif.Av1DistanceWeightedCompoundSequenceNativeReference,
@@ -728,7 +766,7 @@ public class Av1ReconstructionConformanceTests
DistanceWeightedCompoundCoverage,
comparePresentation);
- ValidateSelectableCompoundSequence(
+ ValidateInterPredictionSequence(
configuration,
TestImages.Heif.Av1WedgeCompoundSequenceAvif,
TestImages.Heif.Av1WedgeCompoundSequenceNativeReference,
@@ -736,7 +774,7 @@ public class Av1ReconstructionConformanceTests
WedgeCompoundCoverage | InvertedWedgeCompoundCoverage,
comparePresentation);
- ValidateSelectableCompoundSequence(
+ ValidateInterPredictionSequence(
configuration,
TestImages.Heif.Av1DifferenceWeightedCompoundSequenceAvif,
TestImages.Heif.Av1DifferenceWeightedCompoundSequenceNativeReference,
@@ -744,7 +782,7 @@ public class Av1ReconstructionConformanceTests
DifferenceWeightedCompoundCoverage | InvertedDifferenceWeightedCompoundCoverage,
comparePresentation);
- ValidateSelectableCompoundSequence(
+ ValidateInterPredictionSequence(
configuration,
TestImages.Heif.Av1InterIntraSequenceAvif,
TestImages.Heif.Av1InterIntraSequenceNativeReference,
@@ -756,7 +794,7 @@ public class Av1ReconstructionConformanceTests
///
/// Decodes one complete retained-reference sequence and compares its final native and presented samples exactly.
///
- private static void ValidateSelectableCompoundSequence(
+ private static void ValidateInterPredictionSequence(
Configuration configuration,
string imagePath,
string nativeReferencePath,
@@ -800,7 +838,7 @@ public class Av1ReconstructionConformanceTests
track.CicpProfile,
track.Av1CodecConfiguration);
- coverage |= GetSelectableCompoundCoverage(decoder);
+ coverage |= GetInterPredictionCoverage(decoder);
continue;
}
@@ -810,9 +848,9 @@ public class Av1ReconstructionConformanceTests
track.Av1CodecConfiguration);
Av1FrameBuffer frameBuffer = Assert.IsType>(decoder.FrameBuffer);
- coverage |= GetSelectableCompoundCoverage(decoder);
+ coverage |= GetInterPredictionCoverage(decoder);
- // Every compound branch retains the same row-addressed plane contract under constrained allocators.
+ // Every inter-prediction 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);
@@ -845,9 +883,9 @@ public class Av1ReconstructionConformanceTests
}
///
- /// Collects the selectable compound and inter-intra modes retained in one decoded frame.
+ /// Collects the selectable compound, inter-intra, and OBMC modes retained in one decoded frame.
///
- private static int GetSelectableCompoundCoverage(Av1Decoder decoder)
+ private static int GetInterPredictionCoverage(Av1Decoder decoder)
{
ObuSequenceHeader sequenceHeader = Assert.IsType(decoder.SequenceHeader);
Av1FrameInfo frameInfo = Assert.IsType(decoder.FrameInfo);
@@ -862,6 +900,11 @@ public class Av1ReconstructionConformanceTests
Av1SuperblockInfo superblockInfo = frameInfo.GetSuperblock(new Point(superblockColumn, superblockRow));
foreach (Av1BlockModeInfo modeInfo in superblockInfo.GetModeInfos())
{
+ if (modeInfo.MotionMode == Av1MotionMode.Obmc)
+ {
+ coverage |= ObmcCoverage;
+ }
+
if (modeInfo.ReferenceFrames[1] == Av1ReferenceFrameType.Intra)
{
coverage |= modeInfo.UseInterIntraWedge ? WedgeInterIntraCoverage : SmoothInterIntraCoverage;
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index e4c24f804..f80d087f8 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -1372,6 +1372,9 @@ public static class TestImages
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 Av1ObmcSequenceAvif = "Heif/Av1/Conformance/libavif-webp-logo-obmc.avif";
+ public const string Av1ObmcSequenceNativeReference = "Heif/Av1/Conformance/libavif-webp-logo-obmc-libaom.y4m";
+ public const string Av1ObmcSequencePresentationReference = "Heif/Av1/Conformance/libavif-webp-logo-obmc-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 8cfa1250d..af55cf261 100644
--- a/tests/Images/Input/Heif/Av1/Conformance/README.md
+++ b/tests/Images/Input/Heif/Av1/Conformance/README.md
@@ -42,6 +42,7 @@ The reference builds use `AOM_TARGET_CPU=generic` and disable libyuv. Native rec
| `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 |
+| `libavif-webp-logo-obmc` | Above and left overlapping motion compensation through a 19-frame dependent sequence |
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.
@@ -101,6 +102,23 @@ Pinned scalar libavif generated each final native and presentation reference wit
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.
+## Overlapping motion-compensation fixture
+
+The `libavif-webp-logo-obmc.avif` fixture uses the same pinned `tests/data/webp_logo_animated.y4m` source and source SHA-256 as the compound fixtures. It was encoded with the pinned scalar toolchain after disabling competing compound, inter-intra, warped, and global prediction modes:
+
+```text
+./avifenc -j 1 -c aom -s 0 -q 80 -a max-reference-frames=3 -a enable-dist-wtd-comp=0 -a enable-masked-comp=0 -a enable-interintra-comp=0 -a enable-warped-motion=0 -a enable-global-motion=0 tests/data/webp_logo_animated.y4m libavif-webp-logo-obmc.avif
+```
+
+Pinned scalar libavif generated the final native and presentation references with:
+
+```text
+./avifdec -j 1 -c aom --index 18 libavif-webp-logo-obmc.avif libavif-webp-logo-obmc-libaom.y4m
+./avifdec -j 1 -c aom --index 18 libavif-webp-logo-obmc.avif libavif-webp-logo-obmc-libavif.png
+```
+
+The AVIF SHA-256 is `765245F71BD398F7AD87BD83FD5C5C11172981B37A4FABF4F10F65D4E8AEA537`. The retained frame-18 Y4M SHA-256 is `904D1B5B3E7F334CE8D44040F9A7BDCAC1F7773122FF1C5F06A5B4DD31A62A97`, and the frame-18 PNG SHA-256 is `D2CB388C9092EF17C4F0382C0150DD30D6F9D0EE247FF45AB5D7D4D312CEB23C`. Pinned libaom block tracing records more than one hundred actual OBMC blocks across the decoded sequence, including blocks with nonzero horizontal and vertical motion vectors. The production test requires decoded OBMC mode state, decodes every retained-reference dependency, compares the final native Y, U, and V planes exactly, compares the final RGBA presentation exactly through `FeatureTestRunner`, and repeats reconstruction with constrained tracked allocation. Direct production-branch tests separately cover 8/10/12-bit storage and 4:2:0 and 4:2:2 overlap geometry.
+
## 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-obmc-libaom.y4m b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-obmc-libaom.y4m
new file mode 100644
index 000000000..2d3f2f87e
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-obmc-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-obmc-libavif.png b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-obmc-libavif.png
new file mode 100644
index 000000000..c94b799dc
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-obmc-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-obmc.avif b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-obmc.avif
new file mode 100644
index 000000000..192622490
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-webp-logo-obmc.avif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:765245f71bd398f7ad87bd83fd5c5c11172981b37a4fabf4f10f65d4e8aea537
+size 6452