Browse Source

Complete AV1 palette memory checkpoint

pull/2633/head
James Jackson-South 2 days ago
parent
commit
c4b4e4e038
  1. 75
      HEIF_IMPLEMENTATION_PLAN.md
  2. 2
      src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
  3. 6
      src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs
  4. 215
      src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
  5. 14
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
  6. 2
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
  7. 2
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
  8. 2
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolReader.cs
  9. 25
      src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometryFactory.cs
  10. 2
      src/ImageSharp/Formats/Heif/Av1/Motion/Av1GlobalMotionParameters.cs
  11. 8
      src/ImageSharp/Formats/Heif/Av1/Motion/Av1IntraBlockCopy.cs
  12. 8
      src/ImageSharp/Formats/Heif/Av1/Motion/Av1ReferenceMotionVectors.cs
  13. 2
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameReferenceState.cs
  14. 20
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
  15. 2
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSkipModeParameters.cs
  16. 2
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
  17. 68
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs
  18. 3
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs
  19. 2
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Cdef/Av1CdefDecoder.cs
  20. 14
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Cdef/Av1CdefFilter.cs
  21. 2
      src/ImageSharp/Formats/Heif/Av1/Pipeline/FilmGrain/Av1FilmGrainDecoder.cs
  22. 4
      src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1DeblockingFilter.cs
  23. 10
      src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs
  24. 2
      src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopRestoration/Av1SelfGuidedFilter.Operations.cs
  25. 2
      src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopRestoration/Av1WienerFilter.cs
  26. 1
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1DeQuantizationContext.cs
  27. 2
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1InverseQuantizer.cs
  28. 2
      src/ImageSharp/Formats/Heif/Av1/Pipeline/SuperResolution/Av1SuperResolutionDecoder.cs
  29. 2
      src/ImageSharp/Formats/Heif/Av1/Pipeline/SuperResolution/Av1SuperResolutionFilter.cs
  30. 2
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1ChromaPredictionModeExtensions.cs
  31. 2
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.Operations.cs
  32. 10
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
  33. 2
      src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.cs
  34. 2
      src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterIntraMaskBuilder.cs
  35. 4
      src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.Operator.cs
  36. 2
      src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.TwoDimensions.Byte.cs
  37. 2
      src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.cs
  38. 2
      src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1ReferenceScale.cs
  39. 8
      src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1WedgeMask.cs
  40. 2
      src/ImageSharp/Formats/Heif/Av1/Prediction/IntraBlockCopy/Av1IntraBlockCopyBilinearPredictor.Operator.cs
  41. 8
      src/ImageSharp/Formats/Heif/Av1/ReferenceFrames/Av1ReferenceFrameDerivation.cs
  42. 2
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs
  43. 20
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.MotionField.cs
  44. 81
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs
  45. 194
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
  46. 107
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
  47. 18
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs
  48. 10
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs
  49. 2
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseWalshHadamardTransformer.cs
  50. 8
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform1dMath.cs
  51. 18
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dOperations.cs
  52. 2
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformWorkspace.cs
  53. 6
      src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Inverse2dTransformer.Adst4Operator.cs
  54. 2
      src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Inverse2dTransformer.Identity16Operator.cs
  55. 2
      src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Inverse2dTransformer.Identity4Operator.cs
  56. 6
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CdefFilterTests.cs
  57. 6
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ChromaFromLumaTests.cs
  58. 26
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs
  59. 26
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundInterPredictorTests.cs
  60. 12
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundReferenceEntropyTests.cs
  61. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs
  62. 6
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs
  63. 4
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1GlobalMotionParametersTests.cs
  64. 14
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterFrameIntraEntropyTests.cs
  65. 10
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterFrameModeInfoTests.cs
  66. 8
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterIntraEntropyTests.cs
  67. 8
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterModeEntropyTests.cs
  68. 12
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterPredictorTests.cs
  69. 8
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterpolationFilterEntropyTests.cs
  70. 4
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraBlockCopyPredictorTests.cs
  71. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraBlockCopyTests.cs
  72. 104
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraPredictionMemory.cs
  73. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseQuantizationTests.cs
  74. 12
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs
  75. 12
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeEntropyTests.cs
  76. 6
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeInfoTests.cs
  77. 4
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionVectorEntropyTests.cs
  78. 4
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionVectorTests.cs
  79. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PalettePredictorTests.cs
  80. 24
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs
  81. 291
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
  82. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceFrameStoreTests.cs
  83. 4
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs
  84. 20
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScaledInterPredictorTests.cs
  85. 4
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SelectableCompoundEntropyTests.cs
  86. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SelfGuidedFilterTests.cs
  87. 22
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SingleReferenceEntropyTests.cs
  88. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SymbolContextTests.cs
  89. 4
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TemporalSegmentationTests.cs
  90. 6
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs
  91. 44
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1WarpedInterPredictorTests.cs
  92. 10
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1YuvConverterTests.cs
  93. 18
      tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs
  94. 6
      tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameLifecycleTests.cs
  95. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/ObuSkipModeParametersTests.cs
  96. 4
      tests/ImageSharp.Tests/TestImages.cs
  97. 0
      tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeCdefMatchesReference_Rgba32_libavif-cdef-cosmos-10b.png
  98. 0
      tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeCdefMatchesReference_Rgba32_libavif-cdef-cosmos-12b.png
  99. 0
      tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeCdefMatchesReference_Rgba32_libavif-cdef-kodim23-8b.png
  100. 0
      tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeEqualAverageCompoundMatchesReference_Rgba32_libavif-webp-logo-average-compound.png

75
HEIF_IMPLEMENTATION_PLAN.md

@ -82,12 +82,12 @@ Recovered task-history evidence from 2026-08-31:
### 2. Correct the single-reference inter-frame checkpoint ### 2. Correct the single-reference inter-frame checkpoint
The checkpoint implementation is complete. The first two inherited audit findings were rechecked against current libaom `main` and did not require production changes; the remaining production boundary, reconstruction, and ownership work is now implemented and verified. The committed checkpoint remains valid through `57a3f6668e39d0934e7b6b8d37a3dc2a5adc88f0`. The current checkpoint replaces frame-sized palette maps with fixed decoder-session scratch, reconstructs each superblock before reusing that scratch, and passes the ownership, documentation, full AV1 test, and Release source-build gates on both target frameworks.
- [x] Reconcile interpolation-filter syntax in `Av1TileReader` with current libaom `main`. - [x] Reconcile interpolation-filter syntax in `Av1TileReader` with current libaom `main`.
- Current libaom `av1_is_interp_needed` calls `is_nontrans_global_motion`, whose loop rejects only `TRANSLATION`. Identity GLOBALMV therefore omits switchable-filter symbols. - Current libaom `av1_is_interp_needed` calls `is_nontrans_global_motion`, whose loop rejects only `TRANSLATION`. Identity GLOBALMV therefore omits switchable-filter symbols.
- Current `Av1TileReader` uses the same non-Translation classification. The existing Identity test leaves sentinel filter symbols unread, while the Translation test consumes them. - Current `Av1TileReader` uses the same non-Translation classification. The existing Identity test leaves sentinel filter symbols unread, while the Translation test consumes them.
- No production change is required. The stale test comment was changed from “pinned” to “current” libaom. - No production change is required. The focused test describes only the syntax behavior it proves.
- [x] Reconcile both spatial single-reference extension loops in `Av1ReferenceMotionVectors` with current libaom `main`. - [x] Reconcile both spatial single-reference extension loops in `Av1ReferenceMotionVectors` with current libaom `main`.
- Current libaom `setup_ref_mv_list` stops both loops at `MAX_MV_REF_CANDIDATES`, which is two. `MAX_REF_MV_STACK_SIZE`, which is eight, is the stack capacity used by the earlier direct and temporal candidate collection; it is not the stop condition for these two extension loops. - Current libaom `setup_ref_mv_list` stops both loops at `MAX_MV_REF_CANDIDATES`, which is two. `MAX_REF_MV_STACK_SIZE`, which is eight, is the stack capacity used by the earlier direct and temporal candidate collection; it is not the stop condition for these two extension loops.
- Current `Av1ReferenceMotionVectors` uses the same two-entry stop condition and retains an eight-entry stack for earlier candidates and DRL selection. - Current `Av1ReferenceMotionVectors` uses the same two-entry stop condition and retains an eight-entry stack for earlier candidates and DRL selection.
@ -102,10 +102,10 @@ The checkpoint implementation is complete. The first two inherited audit finding
- Compare the final frame's native Y, Cb, and Cr planes exactly with current-main libaom output. - Compare the final frame's native Y, Cb, and Cr planes exactly with current-main libaom output.
- Compare the final presented image through the established ImageSharp reference-image comparison API. - Compare the final presented image through the established ImageSharp reference-image comparison API.
- Do not substitute an internal helper test, fake tile reader, non-zero assertion, custom pixel loop, or tolerant comparison. - Do not substitute an internal helper test, fake tile reader, non-zero assertion, custom pixel loop, or tolerant comparison.
- [x] Prove motion-field ownership and lifetime. - [x] Prove motion-field ownership and lifetime after the current reconstruction-timing change.
- Track initialization, retained-slot aliases, failure unwinding, presentation ownership, decoder-result ownership, and final disposal. - Track initialization, retained-slot aliases, failure unwinding, presentation ownership, decoder-result ownership, and final disposal.
- Every allocator-owned object must be returned exactly once. - Every allocator-owned object must be returned exactly once.
- [x] Correct stale documentation. - [x] Correct stale documentation for the current worktree.
- Av1InterFrameModeInfoTests must describe the behavior it actually proves. - Av1InterFrameModeInfoTests must describe the behavior it actually proves.
- Do not claim production reconstruction, constrained allocation, ownership, or reference-stack coverage unless the test executes that contract. - Do not claim production reconstruction, constrained allocation, ownership, or reference-stack coverage unless the test executes that contract.
@ -117,20 +117,20 @@ Checkpoint gate:
- [x] The established exact presentation comparison passes. - [x] The established exact presentation comparison passes.
- [x] Normal, AVX-512-disabled, AVX-disabled, and scalar FeatureTestRunner configurations pass where supported. - [x] Normal, AVX-512-disabled, AVX-disabled, and scalar FeatureTestRunner configurations pass where supported.
- [x] Constrained allocation preserves the enforced single-group plane invariant without copying or per-block allocation. - [x] Constrained allocation preserves the enforced single-group plane invariant without copying or per-block allocation.
- [x] Motion-field allocation tracking is balanced across success and failure. - [x] Motion-field allocation tracking is balanced across success and failure on net10.0 and net11.0.
- [x] Release builds for net10.0 and net11.0 pass with zero errors. - [x] Release source builds pass for net10.0 and net11.0 with zero warnings and zero errors.
- [x] Focused Release tests pass with zero failures or skips. - [x] The complete AV1 namespace passes 8,732 of 8,732 tests on net10.0 and net11.0 with zero failures or skips.
- [x] Scoped semantic, StyleCop, whitespace, and git diff checks pass. - [x] Roslynk reports zero compiler errors; scoped analyzer inspection reports no diagnostics introduced by the current changes; `git diff --check` passes.
- [x] The completed checkpoint was committed as `54bb6cbe59bd113058854a3ee31448cf61f462ca` with author and committer `James Jackson-South <james_south@hotmail.com>`. - [x] The completed checkpoint was committed as `54bb6cbe59bd113058854a3ee31448cf61f462ca` with author and committer `James Jackson-South <james_south@hotmail.com>`.
Verified single-reference checkpoint evidence on 2026-08-31: Verified single-reference checkpoint evidence on 2026-08-31:
- The current-main `aomdec` was rebuilt directly from `D:\GitHub\AOMediaCodec\aom` and identified itself as `3.15.0-13-g441c439b99`. - The current-main `aomdec` was rebuilt directly from `D:\GitHub\AOMediaCodec\aom` and identified itself as `3.15.0-13-g441c439b99`.
- Decoding the 72-byte progressive payload with `--all-layers`, one thread, and row multithreading disabled produced 2,178 YUV444 color samples. All samples in both layers match the first three planes of the stored YUV444-alpha reference exactly. - Decoding the 72-byte progressive payload with `--all-layers`, one thread, and row multithreading disabled produced 2,178 YUV444 color samples. All samples in both layers match the first three planes of the stored YUV444-alpha reference exactly.
- `DecodeProgressiveSingleReferenceMatchesCurrentLibaomReferences` executes the production decoder through FeatureTestRunner and compares the complete presented `Rgba32` image with `CompareToReferenceOutput(ImageComparer.Exact, provider)`. The redundant manual alpha loop was removed. - `DecodeProgressiveSingleMatchesReference` executes the production decoder through FeatureTestRunner and compares the complete presented `Rgba32` image with `CompareToReferenceOutput(ImageComparer.Exact, provider)`. The redundant manual alpha loop was removed.
- `DecodeProgressiveSingleReferenceWithConstrainedAllocator` executes the same production reconstruction with a 1,024-byte allocator group capacity and verifies that every allocation is returned exactly once. - `DecodeProgressiveSingleWithConstrainedAllocator` executes the same production reconstruction with a 1,024-byte allocator group capacity and verifies that every allocation is returned exactly once.
- `MotionFieldsFollowReferenceAliasesAndPresentationOwnership`, `MotionFieldAllocationFailureUnwindsTileReaderOwnership`, `DecodeProgressiveSingleReferenceTracksMotionFieldResultOwnership`, and the reference-store replacement, reset, and transfer tests cover initialization, aliases, presentation ownership, decoder-result ownership, failure unwinding, repeated disposal, and exactly-once final returns. - `MotionFieldsFollowAliasesAndPresentationOwnership`, `MotionFieldAllocationFailureUnwindsTileReaderOwnership`, `DecodeProgressiveSingleTracksMotionFieldOwnership`, and the reference-store replacement, reset, and transfer tests cover initialization, aliases, presentation ownership, decoder-result ownership, failure unwinding, repeated disposal, and exactly-once final returns in the current worktree.
- The focused Release set passed 17 of 17 tests on net10.0 and 17 of 17 tests on net11.0, with zero failures and zero skips. This includes both GLOBALMV syntax cases, spatial extension, plane invariants, production reconstruction, FeatureTestRunner dispatch, and ownership. - The current worktree passes the four-case palette set, seven-case ownership set, and 29-case syntax, plane, and production reconstruction set on both target frameworks. The complete AV1 namespace passes 8,732 of 8,732 tests on net10.0 and net11.0 with zero failures or skips.
- Release source builds passed for net10.0 and net11.0 with zero warnings and zero errors. - Release source builds passed for net10.0 and net11.0 with zero warnings and zero errors.
- Roslynk reported zero compiler errors. The scoped changed-file analyzer inspection reported no StyleCop diagnostics attributable to this checkpoint; its only remaining match is the pre-existing xUnit cancellation warning in an unrelated `HeifDecoderTests` method. - Roslynk reported zero compiler errors. The scoped changed-file analyzer inspection reported no StyleCop diagnostics attributable to this checkpoint; its only remaining match is the pre-existing xUnit cancellation warning in an unrelated `HeifDecoderTests` method.
- `git diff --check` passed, and neither `.gitattributes` file changed. - `git diff --check` passed, and neither `.gitattributes` file changed.
@ -148,7 +148,7 @@ $env:COMPlus_DbgEnableMiniDump = '0'
$env:DOTNET_EnableCrashReport = '0' $env:DOTNET_EnableCrashReport = '0'
$env:COMPlus_EnableCrashReport = '0' $env:COMPlus_EnableCrashReport = '0'
$heifCheckpointFilter = 'FullyQualifiedName~Av1InterFrameModeInfoTests.ReadInterFrameModeInfoReadsInterpolationFilters|FullyQualifiedName~Av1InterFrameModeInfoTests.ReadInterFrameModeInfoOmitsInterpolationFiltersForIdentityGlobalMotion|FullyQualifiedName~Av1ReferenceMotionVectorsTests.BuildReversesOppositeDirectionExtensionCandidate|FullyQualifiedName~Av1FrameBufferTests|FullyQualifiedName~Av1ReferenceFrameStoreTests.MotionFieldsFollowReferenceAliasesAndPresentationOwnership|FullyQualifiedName~Av1ReferenceFrameStoreTests.MotionFieldAllocationFailureUnwindsTileReaderOwnership|FullyQualifiedName~Av1ReferenceFrameStoreTests.PartialReplacementPreservesSharedOwner|FullyQualifiedName~Av1ReferenceFrameStoreTests.FinalReplacementReleasesDisplacedOwner|FullyQualifiedName~Av1ReferenceFrameStoreTests.ResetReleasesUniqueOwnersAndClearsSlots|FullyQualifiedName~Av1ReferenceFrameStoreTests.TakeOutputTransfersPlanesAndReleasesOtherReferences|FullyQualifiedName~Av1ReconstructionConformanceTests.DecodeProgressiveSingleReferenceMatchesCurrentLibaomReferences|FullyQualifiedName~Av1ReconstructionConformanceTests.DecodeProgressiveSingleReferenceWithConstrainedAllocator|FullyQualifiedName~Av1ReconstructionConformanceTests.DecodeProgressiveSingleReferenceTracksMotionFieldResultOwnership' $heifCheckpointFilter = 'FullyQualifiedName~Av1InterFrameModeInfoTests.ReadInterFrameModeInfoReadsInterpolationFilters|FullyQualifiedName~Av1InterFrameModeInfoTests.IdentityGlobalMotionOmitsInterpolationFilters|FullyQualifiedName~Av1ReferenceMotionVectorsTests.BuildReversesOppositeDirectionExtensionCandidate|FullyQualifiedName~Av1FrameBufferTests|FullyQualifiedName~Av1ReferenceFrameStoreTests.MotionFieldsFollowAliasesAndPresentationOwnership|FullyQualifiedName~Av1ReferenceFrameStoreTests.MotionFieldAllocationFailureUnwindsTileReaderOwnership|FullyQualifiedName~Av1ReferenceFrameStoreTests.PartialReplacementPreservesSharedOwner|FullyQualifiedName~Av1ReferenceFrameStoreTests.FinalReplacementReleasesDisplacedOwner|FullyQualifiedName~Av1ReferenceFrameStoreTests.ResetReleasesUniqueOwnersAndClearsSlots|FullyQualifiedName~Av1ReferenceFrameStoreTests.TakeOutputTransfersPlanesAndReleasesOtherReferences|FullyQualifiedName~Av1ReconstructionConformanceTests.DecodeProgressiveSingleMatchesReference|FullyQualifiedName~Av1ReconstructionConformanceTests.DecodeProgressiveSingleWithConstrainedAllocator|FullyQualifiedName~Av1ReconstructionConformanceTests.DecodeProgressiveSingleTracksMotionFieldOwnership'
dotnet build src\ImageSharp\ImageSharp.csproj -c Release -f net10.0 --no-restore --disable-build-servers -m:1 --no-incremental --nologo --verbosity:minimal dotnet build src\ImageSharp\ImageSharp.csproj -c Release -f net10.0 --no-restore --disable-build-servers -m:1 --no-incremental --nologo --verbosity:minimal
dotnet build src\ImageSharp\ImageSharp.csproj -c Release -f net11.0 --no-restore --disable-build-servers -m:1 --no-incremental --nologo --verbosity:minimal dotnet build src\ImageSharp\ImageSharp.csproj -c Release -f net11.0 --no-restore --disable-build-servers -m:1 --no-incremental --nologo --verbosity:minimal
@ -563,8 +563,8 @@ Previously verified algorithm checkpoints remain valuable evidence, but the fina
- [x] Intra prediction covers directional, DC, smooth, Paeth, chroma-from-luma, filter-intra, and palette families with the established operator architecture. - [x] Intra prediction covers directional, DC, smooth, Paeth, chroma-from-luma, filter-intra, and palette families with the established operator architecture.
- [x] Intra-block copy has exact native reconstruction and feature-isolated SIMD evidence. - [x] Intra-block copy has exact native reconstruction and feature-isolated SIMD evidence.
- [x] Lossless inverse transform, loop filtering, CDEF, super-resolution, restoration, and film grain have focused checkpoint evidence. - [x] Lossless inverse transform, loop filtering, CDEF, super-resolution, restoration, and film grain have focused checkpoint evidence.
- [~] Retained references, CDF snapshots, segmentation maps, global motion, temporal motion fields, and dependent-frame lifecycle exist locally and require current-tree re-verification. - [x] Retained references, CDF snapshots, segmentation maps, global motion, temporal motion fields, and dependent-frame lifecycle have been re-audited and verified against current libaom `main`.
- [~] All-intra and dependent-frame profile fixtures exist for 8, 10, and 12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 paths. - [~] All-intra and dependent-frame profile fixtures exist for 8, 10, and 12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 paths. Current item.
- [ ] Re-run the exact current-tree native-plane matrix through the production decoder. - [ ] Re-run the exact current-tree native-plane matrix through the production decoder.
- [ ] Re-run the exact current-tree presentation matrix through ImageSharp's established comparison API. - [ ] Re-run the exact current-tree presentation matrix through ImageSharp's established comparison API.
- [ ] Verify malformed/truncated data, frame IDs, reference slots, tile bounds, allocation limits, cancellation, and failure unwinding. - [ ] Verify malformed/truncated data, frame IDs, reference slots, tile bounds, allocation limits, cancellation, and failure unwinding.
@ -626,12 +626,12 @@ Verified partition, mode, segmentation, quantization, and transform checkpoint e
Each transform resets and clears only its active padded geometry, so no transform creates an allocation. Each transform resets and clears only its active padded geometry, so no transform creates an allocation.
Allocation tracking over all eight minimum- and maximum-quantizer frames proves exactly one coefficient Allocation tracking over all eight minimum- and maximum-quantizer frames proves exactly one coefficient
scratch allocation per frame and exactly-once return after decoder disposal. scratch allocation per frame and exactly-once return after decoder disposal.
- [x] Palette index maps are allocator-backed frame surfaces addressed row by row through `Buffer2DRegion`. - [x] Palette index maps use two allocator-backed 128x128 decoder-session scratch buffers, one for luma and
The wavefront context, stable neighbor ordering, right/bottom padding, transform offsets, and prediction one for chroma. Each parsed superblock is reconstructed before either buffer is reused, and each block
were audited against current libaom `av1/decoder/detokenize.c`, `av1/common/entropymode.c`, clears only its transient `Buffer2DRegion` view after prediction. The fixed 32 KiB session cost replaces
`av1/decoder/decodeframe.c`, and `av1/common/reconintra.c`. A 1 KiB constrained allocator forces both the former two full-frame maps without copies or per-block allocations. A 1 KiB constrained allocator
luma and chroma maps across multiple memory groups without copies or per-block allocations and proves splits both buffers across memory groups. The four-case palette set passes on net10.0 and net11.0 with
exactly-once disposal. exact native and presentation output, truncated-entropy rejection, and balanced exactly-once disposal.
- [x] `Av1BlockModeInfo` is value storage, removing the managed object allocation formerly created for every - [x] `Av1BlockModeInfo` is value storage, removing the managed object allocation formerly created for every
decoded coding block. Explicit `ModeInfoIndex` values preserve libaom's mode-info identity semantics at decoded coding block. Explicit `ModeInfoIndex` values preserve libaom's mode-info identity semantics at
prediction-unit loop-filter edges, and the frame map now uses integer offsets so more than 65,535 decoded prediction-unit loop-filter edges, and the frame map now uses integer offsets so more than 65,535 decoded
@ -649,6 +649,39 @@ Verified partition, mode, segmentation, quantization, and transform checkpoint e
- [x] The exact final AV1 namespace passes 8,732 of 8,732 cases on net10.0 and 8,732 of 8,732 cases on - [x] The exact final AV1 namespace passes 8,732 of 8,732 cases on net10.0 and 8,732 of 8,732 cases on
net11.0, with zero failures or skips. Release source builds pass for net10.0 and net11.0 with zero warnings net11.0, with zero failures or skips. Release source builds pass for net10.0 and net11.0 with zero warnings
and zero errors. Roslynk reports zero compiler errors, and scoped analyzer verification reports no changes. and zero errors. Roslynk reports zero compiler errors, and scoped analyzer verification reports no changes.
- [x] The completed checkpoint was committed as `57a3f6668e39d0934e7b6b8d37a3dc2a5adc88f0` with author
and committer `James Jackson-South <james_south@hotmail.com>`.
Verified retained-frame lifecycle checkpoint evidence on 2026-08-31:
- [x] Audited primary-reference entropy selection, independent per-tile CDF starts, context-update-tile
publication, segmentation-map inheritance, reference-map refresh, and show-existing key-frame reset
against current libaom `av1/decoder/decodeframe.c`, `av1/decoder/decodemv.c`,
`av1/decoder/decoder.c`, and `av1/common/entropymode.c`.
- [x] Audited retained motion-vector cells, reference-side classification, projection source ordering,
projection limits, and reference-frame publication against `av1_copy_frame_mvs`,
`av1_calculate_ref_frame_side`, `motion_field_projection`, and `av1_setup_motion_field` in current
libaom. Same-role primary-reference global-motion inheritance remains covered by the exact current-main
global-warp fixture. The observed clean `HEAD` and `origin/main` revision was
`441c439b9916474cac15d2822af47a9ad70674a8`; this is verification evidence, not a pin.
- [x] Current official libaom decoded the retained `cdfupdate`, `mfmv`, `svc-L2T1`, `svc-L1T2`, and
`svc-L2T2` streams with one thread, row threading disabled, and eight-bit output depth. Their generated
Y4M files match the retained references byte for byte at SHA-256
`4FBFF73FF0DE2D9084DAE557D1D4BD677B0486516525BF4D327D2D795D5A7779`,
`F7DB607694818C19E62FD9A27F53E1A3E2D00B72C39C0430C1B26399CC76777D`,
`7A427631ECBF144F435AA4612F1201415FB1A9BCF9A67BA010AEF830B0C3AB81`,
`4012DE2D4AFD095E7BB68EAE18B50B0674781BB4971CECABC0E5471E63373ED3`, and
`1ABB981CFF76BA9557DA437B258D8A95FCA755DED8E3949D857E8388AB1D6AE3`.
- [x] Existing allocation-tracking tests exercise initialization, retained-slot aliases, allocation-failure
unwinding, presentation ownership, decoder-result ownership, repeated disposal, and final exactly-once
return of reference frames, frame-owned motion fields, entropy snapshots, and segmentation maps.
- [x] The focused Release checkpoint set passes 54 of 54 cases on net10.0 and 54 of 54 cases on net11.0,
with zero failures or skips. It includes exact native CDF-update, motion-field, spatial-layer,
temporal-layer, spatial-temporal-layer, progressive dependent-frame, and global-warp production paths,
plus constrained allocator coverage.
- [x] Release source builds pass for net10.0 and net11.0 with zero warnings and zero errors. Roslynk
reports zero compiler errors, scoped analyzer verification reports no changes, `git diff --check`
passes, and `.gitattributes` is unchanged.
Decoder exit gate: Decoder exit gate:

2
src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs

@ -148,7 +148,7 @@ internal static class Av1BlockSizeExtensions
public static int GetSizeGroup(this Av1BlockSize blockSize) public static int GetSizeGroup(this Av1BlockSize blockSize)
{ {
// AV1 section 9.3 groups a block by its smaller dimension in 4x4 units and caps that logarithm at three. // AV1 section 9.3 groups a block by its smaller dimension in 4x4 units and caps that logarithm at three.
// Deriving the value from the existing geometry tables exactly matches libaom's size_group_lookup table. // Deriving the value from the existing geometry tables exactly matches the reference decoder's size_group_lookup table.
return Math.Min(3, Math.Min(blockSize.Get4x4WidthLog2(), blockSize.Get4x4HeightLog2())); return Math.Min(3, Math.Min(blockSize.Get4x4WidthLog2(), blockSize.Get4x4HeightLog2()));
} }

6
src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs

@ -427,7 +427,7 @@ internal sealed class Av1CodecConfiguration
throw new InvalidImageContentException($"The {sourceName} contains an OBU with a set forbidden header bit."); throw new InvalidImageContentException($"The {sourceName} contains an OBU with a set forbidden header bit.");
} }
// Current libaom deliberately ignores obu_reserved_1bit. The bit does not alter the OBU boundary or // The reference decoder deliberately ignores obu_reserved_1bit. The bit does not alter the OBU boundary or
// decoded syntax, so the bounded container scan must not reject data that the production parser accepts. // decoded syntax, so the bounded container scan must not reject data that the production parser accepts.
ObuType type = (ObuType)((header >> 3) & 0x0F); ObuType type = (ObuType)((header >> 3) & 0x0F);
bool hasExtension = (header & 0x04) != 0; bool hasExtension = (header & 0x04) != 0;
@ -440,7 +440,7 @@ internal sealed class Av1CodecConfiguration
throw new InvalidImageContentException($"The {sourceName} contains a truncated OBU extension header."); throw new InvalidImageContentException($"The {sourceName} contains a truncated OBU extension header.");
} }
// extension_header_reserved_3bits is also consumed but ignored by current libaom. // extension_header_reserved_3bits is also consumed but ignored by the reference decoder.
extension = data[offset++]; extension = data[offset++];
} }
@ -629,7 +629,7 @@ internal sealed class Av1CodecConfiguration
} }
} }
// Both fixed HDR structures end on a byte boundary. libaom accepts zero padding after the required 0x80 byte, // Both fixed HDR structures end on a byte boundary. Zero padding after the required 0x80 byte is accepted,
// so locate the last nonzero byte rather than assuming the OBU payload ends immediately after trailing_bits(). // so locate the last nonzero byte rather than assuming the OBU payload ends immediately after trailing_bits().
if (lastNonzeroByte != 0x80) if (lastNonzeroByte != 0x80)
{ {

215
src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs

@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline;
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.FilmGrain; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.FilmGrain;
using SixLabors.ImageSharp.Formats.Heif.Av1.ReferenceFrames; using SixLabors.ImageSharp.Formats.Heif.Av1.ReferenceFrames;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp; using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -30,6 +31,16 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
/// </summary> /// </summary>
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary>
/// Reusable luma palette indices for the coding blocks in one superblock.
/// </summary>
private readonly Buffer2D<byte> lumaPaletteColorIndexMap;
/// <summary>
/// Reusable chroma palette indices for the coding blocks in one superblock.
/// </summary>
private readonly Buffer2D<byte> chromaPaletteColorIndexMap;
/// <summary> /// <summary>
/// The reconstructed references and selected presentation output owned by the current bounded decode session. /// The reconstructed references and selected presentation output owned by the current bounded decode session.
/// </summary> /// </summary>
@ -66,6 +77,16 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
/// </summary> /// </summary>
private Av1TileReader? tileReader; private Av1TileReader? tileReader;
/// <summary>
/// The destination sample buffer for the frame currently being parsed and reconstructed.
/// </summary>
private Av1FrameBuffer<byte>? frameBuffer;
/// <summary>
/// The reconstruction pipeline for the frame currently being parsed.
/// </summary>
private Av1FrameDecoder? frameDecoder;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Av1Decoder"/> class. /// Initializes a new instance of the <see cref="Av1Decoder"/> class.
/// </summary> /// </summary>
@ -84,6 +105,27 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
{ {
this.configuration = configuration; this.configuration = configuration;
this.obuReader = new(operatingPointIndex, this.referenceFrames); this.obuReader = new(operatingPointIndex, this.referenceFrames);
Buffer2D<byte>? lumaPaletteColorIndexMap = null;
Buffer2D<byte>? chromaPaletteColorIndexMap = null;
try
{
// Sequential tile decoding needs only the palette indices belonging to the current superblock. Keeping
// two maximum-superblock surfaces at decoder scope makes the memory bound independent of frame size and
// reuses the same allocator rents across every frame in the bounded sequence.
int paletteMapLength = 1 << Av1Constants.MaxSuperBlockSizeLog2;
lumaPaletteColorIndexMap = configuration.MemoryAllocator.Allocate2D<byte>(paletteMapLength, paletteMapLength);
chromaPaletteColorIndexMap = configuration.MemoryAllocator.Allocate2D<byte>(paletteMapLength, paletteMapLength);
this.lumaPaletteColorIndexMap = lumaPaletteColorIndexMap;
this.chromaPaletteColorIndexMap = chromaPaletteColorIndexMap;
}
catch
{
chromaPaletteColorIndexMap?.Dispose();
lumaPaletteColorIndexMap?.Dispose();
throw;
}
} }
/// <summary> /// <summary>
@ -506,6 +548,10 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
{ {
// A failed frame may own pooled neighbor contexts while earlier layers own reconstructed references and // A failed frame may own pooled neighbor contexts while earlier layers own reconstructed references and
// published CDF snapshots. None can be reused after a non-transactional frame transition has failed. // published CDF snapshots. None can be reused after a non-transactional frame transition has failed.
this.frameDecoder?.Dispose();
this.frameDecoder = null;
this.frameBuffer?.Dispose();
this.frameBuffer = null;
this.tileReader?.Dispose(); this.tileReader?.Dispose();
this.tileReader = null; this.tileReader = null;
this.obuReader.Reset(); this.obuReader.Reset();
@ -527,6 +573,66 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
} }
} }
/// <summary>
/// Validates the active sequence against its container declarations before reconstruction begins.
/// </summary>
/// <param name="sequenceHeader">The active sequence header.</param>
private void ValidateSequence(ObuSequenceHeader sequenceHeader)
{
if (ReferenceEquals(this.validatedSequenceHeader, sequenceHeader))
{
return;
}
this.codecConfiguration?.Validate(sequenceHeader);
CicpProfile? colorProfile = this.containerColorProfile;
if (colorProfile is not null)
{
ObuColorConfig colorConfig = sequenceHeader.ColorConfig;
ObuColorPrimaries containerColorPrimaries = (ObuColorPrimaries)colorProfile.ColorPrimaries;
ObuTransferCharacteristics containerTransferCharacteristics = (ObuTransferCharacteristics)colorProfile.TransferCharacteristics;
ObuMatrixCoefficients containerMatrixCoefficients = (ObuMatrixCoefficients)colorProfile.MatrixCoefficients;
// AV1-ISOBMFF permits nclx to supply only bitstream fields explicitly coded as unspecified. A different
// specified value is a conformance error rather than a container-level color override.
if (colorConfig.ColorPrimaries == ObuColorPrimaries.Unspecified)
{
colorConfig.ColorPrimaries = containerColorPrimaries;
}
else if (colorConfig.ColorPrimaries != containerColorPrimaries)
{
throw new InvalidImageContentException("The HEIF CICP color primaries do not match the AV1 sequence header.");
}
if (colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Unspecified)
{
colorConfig.TransferCharacteristics = containerTransferCharacteristics;
}
else if (colorConfig.TransferCharacteristics != containerTransferCharacteristics)
{
throw new InvalidImageContentException("The HEIF CICP transfer characteristics do not match the AV1 sequence header.");
}
if (colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Unspecified)
{
colorConfig.MatrixCoefficients = containerMatrixCoefficients;
}
else if (colorConfig.MatrixCoefficients != containerMatrixCoefficients)
{
throw new InvalidImageContentException("The HEIF CICP matrix coefficients do not match the AV1 sequence header.");
}
if (colorConfig.ColorRange != colorProfile.FullRange)
{
throw new InvalidImageContentException("The HEIF CICP color range does not match the AV1 sequence header.");
}
}
// The same sequence header governs subsequent layered frames until another header OBU replaces it.
this.validatedSequenceHeader = sequenceHeader;
}
/// <summary> /// <summary>
/// Parses one entropy-coded tile payload into the current frame state. /// Parses one entropy-coded tile payload into the current frame state.
/// </summary> /// </summary>
@ -540,6 +646,7 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
ObuFrameHeader? frameHeader = this.obuReader.FrameHeader; ObuFrameHeader? frameHeader = this.obuReader.FrameHeader;
Guard.NotNull(sequenceHeader, nameof(sequenceHeader)); Guard.NotNull(sequenceHeader, nameof(sequenceHeader));
Guard.NotNull(frameHeader, nameof(frameHeader)); Guard.NotNull(frameHeader, nameof(frameHeader));
this.ValidateSequence(sequenceHeader);
if (!ReferenceEquals(this.entropySequenceHeader, sequenceHeader)) if (!ReferenceEquals(this.entropySequenceHeader, sequenceHeader))
{ {
@ -579,7 +686,18 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
frameHeader, frameHeader,
entropyContexts, entropyContexts,
primaryReferenceContext, primaryReferenceContext,
this.referenceFrames); this.referenceFrames,
this.lumaPaletteColorIndexMap,
this.chromaPaletteColorIndexMap);
this.frameBuffer = new Av1FrameBuffer<byte>(this.configuration, sequenceHeader, sequenceHeader.ColorConfig.GetColorFormat(), false)
{
Width = frameHeader.FrameSize.FrameWidth,
Height = frameHeader.FrameSize.FrameHeight
};
this.frameDecoder = new Av1FrameDecoder(sequenceHeader, frameHeader, this.tileReader.FrameInfo, this.frameBuffer, this.referenceFrames);
this.tileReader.FrameDecoder = this.frameDecoder;
} }
this.tileReader.ReadTile(tileData, tileNum); this.tileReader.ReadTile(tileData, tileNum);
@ -592,65 +710,15 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
{ {
ObuSequenceHeader sequenceHeader = this.obuReader.SequenceHeader!; ObuSequenceHeader sequenceHeader = this.obuReader.SequenceHeader!;
ObuFrameHeader frameHeader = this.obuReader.FrameHeader!; ObuFrameHeader frameHeader = this.obuReader.FrameHeader!;
Av1FrameBuffer<byte>? frameBuffer = null; Av1FrameBuffer<byte>? frameBuffer = this.frameBuffer;
this.frameBuffer = null;
Av1FrameDecoder? frameDecoder = this.frameDecoder;
this.frameDecoder = null;
Av1FrameBuffer<byte>? presentationBuffer = null; Av1FrameBuffer<byte>? presentationBuffer = null;
try try
{ {
if (!ReferenceEquals(this.validatedSequenceHeader, sequenceHeader)) this.ValidateSequence(sequenceHeader);
{
this.codecConfiguration?.Validate(sequenceHeader);
CicpProfile? colorProfile = this.containerColorProfile;
if (colorProfile is not null)
{
ObuColorConfig colorConfig = sequenceHeader.ColorConfig;
ObuColorPrimaries containerColorPrimaries = (ObuColorPrimaries)colorProfile.ColorPrimaries;
ObuTransferCharacteristics containerTransferCharacteristics =
(ObuTransferCharacteristics)colorProfile.TransferCharacteristics;
ObuMatrixCoefficients containerMatrixCoefficients =
(ObuMatrixCoefficients)colorProfile.MatrixCoefficients;
// AV1-ISOBMFF permits nclx to supply only bitstream fields explicitly coded as unspecified. A
// different specified value is a conformance error rather than a container-level color override.
if (colorConfig.ColorPrimaries == ObuColorPrimaries.Unspecified)
{
colorConfig.ColorPrimaries = containerColorPrimaries;
}
else if (colorConfig.ColorPrimaries != containerColorPrimaries)
{
throw new InvalidImageContentException("The HEIF CICP color primaries do not match the AV1 sequence header.");
}
if (colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Unspecified)
{
colorConfig.TransferCharacteristics = containerTransferCharacteristics;
}
else if (colorConfig.TransferCharacteristics != containerTransferCharacteristics)
{
throw new InvalidImageContentException("The HEIF CICP transfer characteristics do not match the AV1 sequence header.");
}
if (colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Unspecified)
{
colorConfig.MatrixCoefficients = containerMatrixCoefficients;
}
else if (colorConfig.MatrixCoefficients != containerMatrixCoefficients)
{
throw new InvalidImageContentException("The HEIF CICP matrix coefficients do not match the AV1 sequence header.");
}
if (colorConfig.ColorRange != colorProfile.FullRange)
{
throw new InvalidImageContentException("The HEIF CICP color range does not match the AV1 sequence header.");
}
}
// The same sequence header governs subsequent layered frames until another header OBU replaces it.
// Retaining the validated reference prevents repeated item/color checks in the common layered case.
this.validatedSequenceHeader = sequenceHeader;
}
if (frameHeader.ShowExistingFrame) if (frameHeader.ShowExistingFrame)
{ {
@ -660,7 +728,7 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
if (existingFrameHeader.FrameType == ObuFrameType.KeyFrame) if (existingFrameHeader.FrameType == ObuFrameType.KeyFrame)
{ {
// libaom resets both the decoder working context and the context retained by the newly aliased key // Both the decoder working context and the context retained by the newly aliased key frame reset
// frame. Later primary-reference selection must therefore observe normative defaults. // frame. Later primary-reference selection must therefore observe normative defaults.
existingFrame.ResetEntropyContext(); existingFrame.ResetEntropyContext();
this.entropyContexts?.Reset(); this.entropyContexts?.Reset();
@ -695,28 +763,16 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
} }
Av1TileReader tileReader = this.tileReader!; Av1TileReader tileReader = this.tileReader!;
Av1FrameInfo frameInfo = tileReader.FrameInfo; Av1FrameInfo frameInfo = tileReader.FrameInfo;
frameBuffer = new Av1FrameBuffer<byte>( Av1FrameBuffer<byte> reconstructedFrameBuffer = frameBuffer!;
this.configuration, frameDecoder!.CompleteFrame();
sequenceHeader,
sequenceHeader.ColorConfig.GetColorFormat(),
false);
// Plane allocations use the sequence maxima, while every reconstruction stage must see the active coded
// dimensions. Super-resolution replaces Width after decoding; Height remains the coded frame height.
frameBuffer.Width = frameHeader.FrameSize.FrameWidth;
frameBuffer.Height = frameHeader.FrameSize.FrameHeight;
using Av1FrameDecoder frameDecoder = new(sequenceHeader, frameHeader, frameInfo, frameBuffer, this.referenceFrames);
frameDecoder.DecodeFrame();
bool retainsReference = (frameHeader.RefreshFrameFlags & byte.MaxValue) != 0; bool retainsReference = (frameHeader.RefreshFrameFlags & byte.MaxValue) != 0;
if (retainsReference) if (retainsReference)
{ {
// Motion compensation may address any clamped position inside the decoder border. Extending once after // Motion compensation may address any clamped position inside the decoder border. Extending once after
// all in-loop filters lets every later block use the full padded span without per-prediction edge copies. // all in-loop filters lets every later block use the full padded span without per-prediction edge copies.
Av1ReferenceFrameBorder.Extend(frameBuffer); Av1ReferenceFrameBorder.Extend(reconstructedFrameBuffer);
} }
bool needsSeparatePresentation = frameHeader.ShowFrame && frameHeader.FilmGrainParameters.ApplyGrain && retainsReference; bool needsSeparatePresentation = frameHeader.ShowFrame && frameHeader.FilmGrainParameters.ApplyGrain && retainsReference;
@ -730,10 +786,10 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
// Film grain must never contaminate a decoded reference. A shown frame that is also refreshed therefore // Film grain must never contaminate a decoded reference. A shown frame that is also refreshed therefore
// receives one allocator-owned presentation copy; frames with no reference role are grained in place. // receives one allocator-owned presentation copy; frames with no reference role are grained in place.
frameBuffer.CopyTo(presentationBuffer); reconstructedFrameBuffer.CopyTo(presentationBuffer);
} }
Av1FrameBuffer<byte> grainTarget = presentationBuffer ?? frameBuffer; Av1FrameBuffer<byte> grainTarget = presentationBuffer ?? reconstructedFrameBuffer;
if (frameHeader.ShowFrame && frameHeader.FilmGrainParameters.ApplyGrain) if (frameHeader.ShowFrame && frameHeader.FilmGrainParameters.ApplyGrain)
{ {
Av1FilmGrainDecoder filmGrainDecoder = new(sequenceHeader, frameHeader, grainTarget); Av1FilmGrainDecoder filmGrainDecoder = new(sequenceHeader, frameHeader, grainTarget);
@ -745,12 +801,12 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
{ {
Av1FrameEntropyContexts entropyContexts = this.entropyContexts!; Av1FrameEntropyContexts entropyContexts = this.entropyContexts!;
Av1FrameEntropyContext entropySnapshot = entropyContexts.RentPublishedSnapshot(); Av1FrameEntropyContext entropySnapshot = entropyContexts.RentPublishedSnapshot();
referenceFrame = new(frameBuffer, frameHeader, frameInfo, entropySnapshot, entropyContexts); referenceFrame = new(reconstructedFrameBuffer, frameHeader, frameInfo, entropySnapshot, entropyContexts);
} }
else else
{ {
// Presentation-only frames can never become primary references, so they own no unused CDF graph. // Presentation-only frames can never become primary references, so they own no unused CDF graph.
referenceFrame = new(frameBuffer, frameHeader, frameInfo); referenceFrame = new(reconstructedFrameBuffer, frameHeader, frameInfo);
} }
frameBuffer = null; frameBuffer = null;
@ -785,6 +841,7 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
// A non-shown frame or failed reconstruction never escapes this callback. The tile reader releases only // A non-shown frame or failed reconstruction never escapes this callback. The tile reader releases only
// its initial frame-state lease; retained frames and the decoder result keep allocator-owned motion fields // its initial frame-state lease; retained frames and the decoder result keep allocator-owned motion fields
// alive independently after the entropy-neighbor contexts are returned. // alive independently after the entropy-neighbor contexts are returned.
frameDecoder?.Dispose();
presentationBuffer?.Dispose(); presentationBuffer?.Dispose();
frameBuffer?.Dispose(); frameBuffer?.Dispose();
this.tileReader?.Dispose(); this.tileReader?.Dispose();
@ -797,10 +854,16 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
this.frameDecoder?.Dispose();
this.frameDecoder = null;
this.frameBuffer?.Dispose();
this.frameBuffer = null;
this.tileReader?.Dispose(); this.tileReader?.Dispose();
this.tileReader = null; this.tileReader = null;
this.referenceFrames.Dispose(); this.referenceFrames.Dispose();
this.FrameInfo?.ReleaseOwner(); this.FrameInfo?.ReleaseOwner();
this.FrameInfo = null; this.FrameInfo = null;
this.lumaPaletteColorIndexMap.Dispose();
this.chromaPaletteColorIndexMap.Dispose();
} }
} }

14
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs

@ -283,7 +283,7 @@ internal static class Av1SymbolContextHelper
int topClass = Math.Min(top, 4); int topClass = Math.Min(top, 4);
int leftClass = Math.Min(left, 4); int leftClass = Math.Min(left, 4);
// AV1 groups each edge into zero, low, or high coefficient-level classes. Retaining libaom's complete table // AV1 groups each edge into zero, low, or high coefficient-level classes. Retaining the reference decoder's complete table
// lets the reader and writer share one compile-time mapping without an encoder-side jagged-array allocation. // lets the reader and writer share one compile-time mapping without an encoder-side jagged-array allocation.
return TransformBlockSkipContexts[(topClass * 5) + leftClass]; return TransformBlockSkipContexts[(topClass * 5) + leftClass];
} }
@ -550,7 +550,7 @@ internal static class Av1SymbolContextHelper
/// <returns>The transform type associated with the selected prediction mode.</returns> /// <returns>The transform type associated with the selected prediction mode.</returns>
public static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType) public static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType)
{ {
// libaom's get_uv_mode() is the explicit boundary between the distinct UV and luma prediction domains. CfL maps // the reference decoder's get_uv_mode() is the explicit boundary between the distinct UV and luma prediction domains. CfL maps
// to DC because the chroma AC contribution is applied to a DC predictor before coefficient reconstruction. // to DC because the chroma AC contribution is applied to a DC predictor before coefficient reconstruction.
Av1PredictionMode mode = planeType == Av1PlaneType.Y ? modeInfo.YMode : modeInfo.UvMode.ToLumaMode(); Av1PredictionMode mode = planeType == Av1PlaneType.Y ? modeInfo.YMode : modeInfo.UvMode.ToLumaMode();
@ -723,7 +723,7 @@ internal static class Av1SymbolContextHelper
} }
// A single intra neighbor uses context two. A single inter neighbor and a block with no neighbors both use // A single intra neighbor uses context two. A single inter neighbor and a block with no neighbors both use
// context zero, matching the unavailable-neighbor behavior in libaom's av1_get_intra_inter_context. // context zero, matching the unavailable-neighbor behavior in the reference decoder's av1_get_intra_inter_context.
if (above is not null) if (above is not null)
{ {
return above.Value.ReferenceFrames[0] <= Av1ReferenceFrameType.Intra ? 2 : 0; return above.Value.ReferenceFrames[0] <= Av1ReferenceFrameType.Intra ? 2 : 0;
@ -745,7 +745,7 @@ internal static class Av1SymbolContextHelper
/// <returns>The context in the inclusive range zero through four.</returns> /// <returns>The context in the inclusive range zero through four.</returns>
public static int GetReferenceModeContext(Av1BlockModeInfo? above, Av1BlockModeInfo? left) public static int GetReferenceModeContext(Av1BlockModeInfo? above, Av1BlockModeInfo? left)
{ {
// Libaom first classifies whether each neighbor uses a second inter reference. Single neighbors then contribute // The reference decoder first classifies whether each neighbor uses a second inter reference. Single neighbors then contribute
// their forward/backward direction, while intra neighbors take the same branch as a non-forward reference. // their forward/backward direction, while intra neighbors take the same branch as a non-forward reference.
if (above is not null && left is not null) if (above is not null && left is not null)
{ {
@ -906,7 +906,7 @@ internal static class Av1SymbolContextHelper
return context + leftFilter; return context + leftFilter;
} }
// The fourth neighbor state is not a selectable Bilinear filter. It is the value libaom uses when a neighbor // The fourth neighbor state is not a selectable Bilinear filter. It is the value the reference decoder uses when a neighbor
// does not share the current primary reference, and when two contributing neighbors selected different filters. // does not share the current primary reference, and when two contributing neighbors selected different filters.
if (leftFilter == SwitchableInterpolationFilterCount) if (leftFilter == SwitchableInterpolationFilterCount)
{ {
@ -995,7 +995,7 @@ internal static class Av1SymbolContextHelper
/// <param name="referenceCounts">The eight-entry reference-count destination indexed by <see cref="Av1ReferenceFrameType"/>.</param> /// <param name="referenceCounts">The eight-entry reference-count destination indexed by <see cref="Av1ReferenceFrameType"/>.</param>
public static void CollectNeighborReferenceCounts(Av1BlockModeInfo? above, Av1BlockModeInfo? left, Span<byte> referenceCounts) public static void CollectNeighborReferenceCounts(Av1BlockModeInfo? above, Av1BlockModeInfo? left, Span<byte> referenceCounts)
{ {
// The caller reuses fixed inline storage across blocks. Clearing all eight entries matches libaom's // The caller reuses fixed inline storage across blocks. Clearing all eight entries matches the reference decoder's
// av1_collect_neighbors_ref_counts and prevents an unavailable neighbor from retaining an earlier block's vote. // av1_collect_neighbors_ref_counts and prevents an unavailable neighbor from retaining an earlier block's vote.
referenceCounts.Clear(); referenceCounts.Clear();
@ -1265,7 +1265,7 @@ internal static class Av1SymbolContextHelper
referenceCounts[(int)referenceFrames[0]]++; referenceCounts[(int)referenceFrames[0]]++;
// A current block may use one reference, but the conditioning neighbors may be compound blocks. Libaom counts // A current block may use one reference, but the conditioning neighbors may be compound blocks. The reference decoder counts
// both labels so later single-reference decisions remain bit-exact when compound support is enabled. // both labels so later single-reference decisions remain bit-exact when compound support is enabled.
if (referenceFrames[1] > Av1ReferenceFrameType.Intra) if (referenceFrames[1] > Av1ReferenceFrameType.Intra)
{ {

2
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs

@ -1086,7 +1086,7 @@ internal ref struct Av1SymbolDecoder
int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint]; int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint];
if (endOfBlockShift > 0) if (endOfBlockShift > 0)
{ {
// The local table retains placeholders for the first three tokens, unlike libaom's compact table, // The local table retains placeholders for the first three tokens, unlike the reference decoder's compact table,
// so the decoded token is also the distribution index. // so the decoded token is also the distribution index.
int endOfBlockContext = endOfBlockPoint; int endOfBlockContext = endOfBlockPoint;
bool bit = this.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext); bool bit = this.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext);

2
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs

@ -387,7 +387,7 @@ internal class Av1SymbolEncoder : IDisposable
int eobShift = eobOffsetBitCount - 1; int eobShift = eobOffsetBitCount - 1;
int bit = Av1Math.GetBit(eobExtra, eobShift); int bit = Av1Math.GetBit(eobExtra, eobShift);
// The local table retains placeholders for the first three tokens, unlike libaom's compact table, // The local table retains placeholders for the first three tokens, unlike the reference decoder's compact table,
// so the encoded token is also the distribution index. // so the encoded token is also the distribution index.
int endOfBlockContext = endOfBlockPosition; int endOfBlockContext = endOfBlockPosition;
w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockContext]); w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockContext]);

2
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolReader.cs

@ -87,7 +87,7 @@ internal ref struct Av1SymbolReader
} }
// The final consumed byte must contain one trailing-one bit at the range decoder's exact stopping position, // The final consumed byte must contain one trailing-one bit at the range decoder's exact stopping position,
// followed only by zero bits. This is the same bounded-stream check performed after libaom decodes a tile. // followed only by zero bits. This is the same bounded-stream check performed after the reference decoder decodes a tile.
int trailingOneBit = 128 >> ((consumedBitCount - 1) & 7); int trailingOneBit = 128 >> ((consumedBitCount - 1) & 7);
int trailingBitMask = (trailingOneBit << 1) - 1; int trailingBitMask = (trailingOneBit << 1) - 1;
if ((this.buffer[consumedByteCount - 1] & trailingBitMask) != trailingOneBit) if ((this.buffer[consumedByteCount - 1] & trailingBitMask) != trailingOneBit)

25
src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometryFactory.cs

@ -125,7 +125,6 @@ internal class Av1BlockGeometryFactory
/// Initializes a new instance of the <see cref="Av1BlockGeometryFactory"/> class. /// Initializes a new instance of the <see cref="Av1BlockGeometryFactory"/> class.
/// </summary> /// </summary>
/// <param name="geom">The predefined geometry used to size and populate the mode-decision scan.</param> /// <param name="geom">The predefined geometry used to size and populate the mode-decision scan.</param>
/// <remarks>SVT-AV1: <c>md_scan_all_blks</c>.</remarks>
public Av1BlockGeometryFactory(Av1GeometryIndex geom) public Av1BlockGeometryFactory(Av1GeometryIndex geom)
{ {
this.blockGeometryModeDecisionScan = new Av1BlockGeometry[MaxBlocksAllocated]; this.blockGeometryModeDecisionScan = new Av1BlockGeometry[MaxBlocksAllocated];
@ -211,8 +210,6 @@ internal class Av1BlockGeometryFactory
// (0)compute total number of blocks using the information provided // (0)compute total number of blocks using the information provided
// maxActiveBlockCount = CountTotalNumberOfActiveBlocks(min_nsq_bsize); // maxActiveBlockCount = CountTotalNumberOfActiveBlocks(min_nsq_bsize);
// if (maxActiveBlockCount != max_block_count)
// SVT_LOG(" \n\n Error %i blocks\n\n ", maxActiveBlockCount);
// (2) Construct md scan blk_geom_mds: use info from dps // (2) Construct md scan blk_geom_mds: use info from dps
int idx_mds = 0; int idx_mds = 0;
this.ScanAllBlocks(ref idx_mds, maxSuperblock, 0, 0, false, 0, min_nsq_bsize); this.ScanAllBlocks(ref idx_mds, maxSuperblock, 0, 0, false, 0, min_nsq_bsize);
@ -224,7 +221,6 @@ internal class Av1BlockGeometryFactory
/// </summary> /// </summary>
/// <param name="min_nsq_bsize">The smallest square size, in pixels, at which non-square partitions remain enabled.</param> /// <param name="min_nsq_bsize">The smallest square size, in pixels, at which non-square partitions remain enabled.</param>
/// <returns>The number of active mode-decision scan entries.</returns> /// <returns>The number of active mode-decision scan entries.</returns>
/// <remarks>SVT-AV1: <c>count_total_num_of_active_blks</c>.</remarks>
private static int CountTotalNumberOfActiveBlocks(int min_nsq_bsize) private static int CountTotalNumberOfActiveBlocks(int min_nsq_bsize)
{ {
int depth_scan_idx = 0; int depth_scan_idx = 0;
@ -273,7 +269,6 @@ internal class Av1BlockGeometryFactory
/// <param name="partitionIterator">The zero-based partition-shape index in scan order.</param> /// <param name="partitionIterator">The zero-based partition-shape index in scan order.</param>
/// <param name="sequenceSize">The width and height, in pixels, of the square being partitioned.</param> /// <param name="sequenceSize">The width and height, in pixels, of the square being partitioned.</param>
/// <returns>The number of component blocks in the partition.</returns> /// <returns>The number of component blocks in the partition.</returns>
/// <remarks>SVT-AV1: <c>get_num_ns_per_part</c>.</remarks>
private static int GetNonSquareCountPerPart(int partitionIterator, int sequenceSize) private static int GetNonSquareCountPerPart(int partitionIterator, int sequenceSize)
{ {
int tot_num_ns_per_part = partitionIterator < 1 ? 1 : partitionIterator < 3 ? 2 : partitionIterator < 5 && sequenceSize < 128 ? 4 : 3; int tot_num_ns_per_part = partitionIterator < 1 ? 1 : partitionIterator < 3 ? 2 : partitionIterator < 5 && sequenceSize < 128 ? 4 : 3;
@ -284,7 +279,6 @@ internal class Av1BlockGeometryFactory
/// Records scan entries that represent the same block size at the same pixel origin. /// Records scan entries that represent the same block size at the same pixel origin.
/// </summary> /// </summary>
/// <param name="max_block_count">The number of populated scan entries to compare.</param> /// <param name="max_block_count">The number of populated scan entries to compare.</param>
/// <remarks>SVT-AV1: <c>log_redundancy_similarity</c>.</remarks>
private static void LogRedundancySimilarity(int max_block_count) private static void LogRedundancySimilarity(int max_block_count)
{ {
for (int blockIterator = 0; blockIterator < max_block_count; blockIterator++) for (int blockIterator = 0; blockIterator < max_block_count; blockIterator++)
@ -315,7 +309,6 @@ internal class Av1BlockGeometryFactory
/// <param name="modeDecisionScanIndex">The zero-based mode-decision scan index.</param> /// <param name="modeDecisionScanIndex">The zero-based mode-decision scan index.</param>
/// <returns>The geometry stored at <paramref name="modeDecisionScanIndex"/>.</returns> /// <returns>The geometry stored at <paramref name="modeDecisionScanIndex"/>.</returns>
/// <exception cref="NotImplementedException">Always thrown because the geometry lookup has not been implemented.</exception> /// <exception cref="NotImplementedException">Always thrown because the geometry lookup has not been implemented.</exception>
/// <remarks>SVT-AV1: <c>get_blk_geom_mds</c>.</remarks>
public static Av1BlockGeometry GetBlockGeometryByModeDecisionScanIndex(int modeDecisionScanIndex) => throw new NotImplementedException(); public static Av1BlockGeometry GetBlockGeometryByModeDecisionScanIndex(int modeDecisionScanIndex) => throw new NotImplementedException();
/// <summary> /// <summary>
@ -377,12 +370,6 @@ internal class Av1BlockGeometryFactory
// this.blockGeometryModeDecisionScan[index].QuadIndex = quadIterator; // this.blockGeometryModeDecisionScan[index].QuadIndex = quadIterator;
// this.blockGeometryModeDecisionScan[index].d1i = depth1Iterator++; // this.blockGeometryModeDecisionScan[index].d1i = depth1Iterator++;
// this.blockGeometryModeDecisionScan[index].sqi_mds = sqi_mds; // this.blockGeometryModeDecisionScan[index].sqi_mds = sqi_mds;
// this.blockGeometryModeDecisionScan[index].svt_aom_geom_idx = svt_aom_geom_idx;
/*
this.blockGeometryModeDecisionScan[index].parent_depth_idx_mds = sqi_mds == 0
? 0
: (sqi_mds + (3 - quad_it) * ns_depth_offset[svt_aom_geom_idx][this.blockGeometryModeDecisionScan[index].Depth]) -
parent_depth_offset[svt_aom_geom_idx][this.blockGeometryModeDecisionScan[index].Depth];*/
this.blockGeometryModeDecisionScan[index].Depth1Offset = this.blockGeometryModeDecisionScan[index].Depth1Offset =
Depth1DepthOffset[(int)geometryIndex][this.blockGeometryModeDecisionScan[index].Depth]; Depth1DepthOffset[(int)geometryIndex][this.blockGeometryModeDecisionScan[index].Depth];
this.blockGeometryModeDecisionScan[index].NextDepthOffset = this.blockGeometryModeDecisionScan[index].NextDepthOffset =
@ -473,17 +460,6 @@ internal class Av1BlockGeometryFactory
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin; this.blockGeometryModeDecisionScan[index].Origin;
} }
/*if (this.blockGeometryModeDecisionScan[index].bsize == BLOCK_16X8)
SVT_LOG("");
this.blockGeometryModeDecisionScan[index].tx_width[tx_depth] =
tx_size_wide[this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth]];
this.blockGeometryModeDecisionScan[index].tx_height[tx_depth] =
tx_size_high[this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth]];
this.blockGeometryModeDecisionScan[index].tx_width_uv[tx_depth] =
tx_size_wide[this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth]];
this.blockGeometryModeDecisionScan[index].tx_height_uv[tx_depth] =
tx_size_high[this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth]];*/
} }
// Transform depth one subdivides eligible luma blocks once while chroma retains its depth-zero size. // Transform depth one subdivides eligible luma blocks once while chroma retains its depth-zero size.
@ -1058,7 +1034,6 @@ internal class Av1BlockGeometryFactory
/// <param name="blockSize">The coded block size whose transform limit is requested.</param> /// <param name="blockSize">The coded block size whose transform limit is requested.</param>
/// <param name="plane">The plane index, where zero selects luma and a positive value selects chroma.</param> /// <param name="plane">The plane index, where zero selects luma and a positive value selects chroma.</param>
/// <returns>The maximum transform size for the selected plane.</returns> /// <returns>The maximum transform size for the selected plane.</returns>
/// <remarks>SVT-AV1: <c>av1_get_tx_size</c>.</remarks>
private static Av1TransformSize GetTransformSize(Av1BlockSize blockSize, int plane) private static Av1TransformSize GetTransformSize(Av1BlockSize blockSize, int plane)
{ {
// Luma uses the coded block's normative transform ceiling directly. // Luma uses the coded block's normative transform ceiling directly.

2
src/ImageSharp/Formats/Heif/Av1/Motion/Av1GlobalMotionParameters.cs

@ -270,7 +270,7 @@ internal struct Av1GlobalMotionParameters
} }
// These biased products are the normative reduced-precision P'P, P'q, and P'r matrices. Computing them // These biased products are the normative reduced-precision P'P, P'q, and P'r matrices. Computing them
// directly preserves libaom's integer least-squares rounding instead of introducing floating-point drift. // directly preserves the reference decoder's integer least-squares rounding instead of introducing floating-point drift.
a00 += LeastSquaresSquare(sourceX); a00 += LeastSquaresSquare(sourceX);
a01 += LeastSquaresProduct1(sourceX, sourceY); a01 += LeastSquaresProduct1(sourceX, sourceY);
a11 += LeastSquaresSquare(sourceY); a11 += LeastSquaresSquare(sourceY);

8
src/ImageSharp/Formats/Heif/Av1/Motion/Av1IntraBlockCopy.cs

@ -89,7 +89,7 @@ internal static class Av1IntraBlockCopy
} }
// The top-left sample begins the outer search region. Sorting the adjacent and outer regions independently // The top-left sample begins the outer search region. Sorting the adjacent and outer regions independently
// preserves libaom's nearest/near ordering while still accumulating repeated vectors across both regions. // preserves the reference decoder's nearest/near ordering while still accumulating repeated vectors across both regions.
AddBlock(ref partitionInfo, -1, -1, tileInfo, candidates, weights, ref candidateCount); AddBlock(ref partitionInfo, -1, -1, tileInfo, candidates, weights, ref candidateCount);
for (int index = 2; index <= ReferenceSearchDistance; index++) for (int index = 2; index <= ReferenceSearchDistance; index++)
{ {
@ -109,7 +109,7 @@ internal static class Av1IntraBlockCopy
SortByWeight(candidates, weights, 0, nearestCandidateCount); SortByWeight(candidates, weights, 0, nearestCandidateCount);
SortByWeight(candidates, weights, nearestCandidateCount, candidateCount); SortByWeight(candidates, weights, nearestCandidateCount, candidateCount);
// Libaom clamps the ranked stack before selecting nearest and near. The displacement entropy syntax is // The reference decoder clamps the ranked stack before selecting nearest and near. The displacement entropy syntax is
// differential, so using an unclamped spatial candidate changes every following component even though the // differential, so using an unclamped spatial candidate changes every following component even though the
// final decoded displacement is validated separately against the stricter intra-block-copy source limits. // final decoded displacement is validated separately against the stricter intra-block-copy source limits.
for (int index = 0; index < candidateCount; index++) for (int index = 0; index < candidateCount; index++)
@ -241,7 +241,7 @@ internal static class Av1IntraBlockCopy
} }
} }
// Libaom uses the four-mode-info-unit step only once the active block reaches 64 samples. // Blocks below 64 samples use the finer two-mode-info-unit scan step.
bool useFourUnitStep = width >= 16; bool useFourUnitStep = width >= 16;
for (int index = 0; index < end;) for (int index = 0; index < end;)
{ {
@ -296,7 +296,7 @@ internal static class Av1IntraBlockCopy
} }
} }
// Libaom uses the four-mode-info-unit step only once the active block reaches 64 samples. // Blocks below 64 samples use the finer two-mode-info-unit scan step.
bool useFourUnitStep = height >= 16; bool useFourUnitStep = height >= 16;
for (int index = 0; index < end;) for (int index = 0; index < end;)
{ {

8
src/ImageSharp/Formats/Heif/Av1/Motion/Av1ReferenceMotionVectors.cs

@ -337,8 +337,8 @@ internal sealed class Av1ReferenceMotionVectors
} }
else else
{ {
// When the direct single-reference stack has fewer than two entries, AV1 extends it from every inter // This fallback supplies only the nearest and near pair when direct and projected scans leave gaps.
// reference in the immediate above and left spatial blocks. Differing reference sign biases are reversed. // Differing reference sign biases are reversed before either candidate enters that pair.
for (int index = 0; Math.Abs(maximumRowOffset) >= 1 && index < extensionLength && this.Count < 2;) for (int index = 0; Math.Abs(maximumRowOffset) >= 1 && index < extensionLength && this.Count < 2;)
{ {
Av1BlockModeInfo candidate = partitionInfo.SuperblockInfo.GetModeInfoAt(new Point(column + index, row - 1)); Av1BlockModeInfo candidate = partitionInfo.SuperblockInfo.GetModeInfoAt(new Point(column + index, row - 1));
@ -377,7 +377,7 @@ internal sealed class Av1ReferenceMotionVectors
} }
// The two-element reference list is separate from the full DRL stack. Missing entries use global motion, // The two-element reference list is separate from the full DRL stack. Missing entries use global motion,
// and both entries undergo the same precision reduction as libaom's av1_find_best_ref_mvs output. // and both entries undergo the same precision reduction as the reference decoder's av1_find_best_ref_mvs output.
this.references[0] = (this.Count > 0 ? this.candidates[0] : globalMotionVector).LowerPrecision( this.references[0] = (this.Count > 0 ? this.candidates[0] : globalMotionVector).LowerPrecision(
frameHeader.AllowHighPrecisionMotionVector, frameHeader.AllowHighPrecisionMotionVector,
frameHeader.ForceIntegerMotionVector); frameHeader.ForceIntegerMotionVector);
@ -1154,7 +1154,7 @@ internal sealed class Av1ReferenceMotionVectors
} }
// The fallback list is positional rather than a weighted candidate scan. Preserve both entries even when // The fallback list is positional rather than a weighted candidate scan. Preserve both entries even when
// they are equal so DRL indices have the same meaning as the current libaom main implementation. // they are equal so the derived DRL indices retain the same meaning.
for (int index = 0; index < 2; index++) for (int index = 0; index < 2; index++)
{ {
this.candidates[index] = primaryList[index]; this.candidates[index] = primaryList[index];

2
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameReferenceState.cs

@ -87,7 +87,7 @@ internal struct ObuFrameReferenceState
if (frameIdNumbersPresent) if (frameIdNumbersPresent)
{ {
// libaom keeps one current_frame_id in decoder-session state. The following header snapshots this value as // the reference decoder keeps one current_frame_id in decoder-session state. The following header snapshots this value as
// its previous identifier before consuming its own current_frame_id syntax. // its previous identifier before consuming its own current_frame_id syntax.
this.CurrentFrameId = frameHeader.CurrentFrameId; this.CurrentFrameId = frameHeader.CurrentFrameId;
this.HasCurrentFrameId = true; this.HasCurrentFrameId = true;

20
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs

@ -204,7 +204,7 @@ internal class ObuReader
// advances once here, so ignored metadata, padding, and reserved OBUs are skipped without copying. // advances once here, so ignored metadata, padding, and reserved OBUs are skipped without copying.
Span<byte> obuPayload = reader.ReadBytes(payloadSize); Span<byte> obuPayload = reader.ReadBytes(payloadSize);
// AV1 operating_point_idc uses bits 0-7 for temporal IDs and bits 8-11 for spatial IDs. libaom // AV1 operating_point_idc uses bits 0-7 for temporal IDs and bits 8-11 for spatial IDs. the reference decoder
// requires both selected bits for an extended OBU, while an all-zero mask and unextended OBUs apply // requires both selected bits for an extended OBU, while an all-zero mask and unextended OBUs apply
// universally. Sequence headers establish the mask and temporal delimiters define framing, so neither // universally. Sequence headers establish the mask and temporal delimiters define framing, so neither
// can be filtered even when their extension identifies a layer outside the selected operating point. // can be filtered even when their extension identifies a layer outside the selected operating point.
@ -294,7 +294,7 @@ internal class ObuReader
} }
// The primary header already owns the decoded frame state. Matching its encoded bytes avoids // The primary header already owns the decoded frame state. Matching its encoded bytes avoids
// parsing the same adaptive frame-header syntax twice, as in libaom's decoder. // parsing the same adaptive frame-header syntax twice.
decodedPayloadSize = primaryFrameHeaderPayload.Length; decodedPayloadSize = primaryFrameHeaderPayload.Length;
break; break;
case ObuType.Frame: case ObuType.Frame:
@ -322,7 +322,7 @@ internal class ObuReader
if (combinedFrameHeader.ShowExistingFrame) if (combinedFrameHeader.ShowExistingFrame)
{ {
// Current libaom permits show_existing_frame only in a standalone frame-header OBU. A // The reference decoder permits show_existing_frame only in a standalone frame-header OBU. A
// combined frame OBU is required to continue with a tile group and therefore cannot use // combined frame OBU is required to continue with a tile group and therefore cannot use
// the header-only retained-frame presentation form. // the header-only retained-frame presentation form.
throw new InvalidImageContentException("A combined AV1 frame OBU cannot display an existing frame."); throw new InvalidImageContentException("A combined AV1 frame OBU cannot display an existing frame.");
@ -350,7 +350,7 @@ internal class ObuReader
} }
// AV1 section 5.6 defines no delimiter syntax. The common post-switch validation still permits // AV1 section 5.6 defines no delimiter syntax. The common post-switch validation still permits
// zero bytes between the empty syntax and the declared payload boundary, matching libaom. // zero bytes between the empty syntax and the declared payload boundary, matching the reference decoder.
decodedPayloadSize = 0; decodedPayloadSize = 0;
break; break;
case ObuType.Padding: case ObuType.Padding:
@ -376,7 +376,7 @@ internal class ObuReader
break; break;
default: default:
// Metadata, tile-list, and reserved OBUs do not contribute to this still-image reconstruction // Metadata, tile-list, and reserved OBUs do not contribute to this still-image reconstruction
// pass. Their declared payload has already been skipped by the parent reader. libaom rejects a // pass. Their declared payload has already been skipped by the parent reader. the reference decoder rejects a
// nonempty unrecognized payload that contains only zeros because it has no trailing one bit. // nonempty unrecognized payload that contains only zeros because it has no trailing one bit.
if (payloadSize > 0) if (payloadSize > 0)
{ {
@ -476,7 +476,7 @@ internal class ObuReader
header.HasExtension = reader.ReadBoolean(); header.HasExtension = reader.ReadBoolean();
header.HasSize = reader.ReadBoolean(); header.HasSize = reader.ReadBoolean();
// Current libaom consumes obu_reserved_1bit without rejecting its value. Reserved fields do not change the // The reference decoder consumes obu_reserved_1bit without rejecting its value. Reserved fields do not change the
// decoded syntax, so accepting either value preserves forward-compatible framing while the forbidden bit // decoded syntax, so accepting either value preserves forward-compatible framing while the forbidden bit
// remains a hard error above. // remains a hard error above.
_ = reader.ReadBoolean(); _ = reader.ReadBoolean();
@ -487,7 +487,7 @@ internal class ObuReader
header.TemporalId = (int)reader.ReadLiteral(3); header.TemporalId = (int)reader.ReadLiteral(3);
header.SpatialId = (int)reader.ReadLiteral(2); header.SpatialId = (int)reader.ReadLiteral(2);
// Current libaom likewise consumes extension_header_reserved_3bits without interpreting their value. // The reference decoder likewise consumes extension_header_reserved_3bits without interpreting their value.
_ = reader.ReadLiteral(3); _ = reader.ReadLiteral(3);
} }
else else
@ -1111,7 +1111,7 @@ internal class ObuReader
ObuFrameSize referenceSize = referenceFrame.FrameHeader.FrameSize; ObuFrameSize referenceSize = referenceFrame.FrameHeader.FrameSize;
// AV1 5.9.7 inherits the reference buffer's visible post-super-resolution dimensions, corresponding to // AV1 5.9.7 inherits the reference buffer's visible post-super-resolution dimensions, corresponding to
// libaom's y_crop_width and y_crop_height, plus its render rectangle. The current frame then signals its own // the reference decoder's y_crop_width and y_crop_height, plus its render rectangle. The current frame then signals its own
// super-resolution denominator, so the reference's coded width and denominator are not copied. // super-resolution denominator, so the reference's coded width and denominator are not copied.
frameSize.FrameWidth = referenceFrame.FrameBuffer.Width; frameSize.FrameWidth = referenceFrame.FrameBuffer.Width;
frameSize.FrameHeight = referenceFrame.FrameBuffer.Height; frameSize.FrameHeight = referenceFrame.FrameBuffer.Height;
@ -1331,7 +1331,7 @@ internal class ObuReader
int tileWidth = (tileInfo.TileColumnStartModeInfo[column + 1] - tileInfo.TileColumnStartModeInfo[column]) int tileWidth = (tileInfo.TileColumnStartModeInfo[column + 1] - tileInfo.TileColumnStartModeInfo[column])
<< Av1Constants.ModeInfoSizeLog2; << Av1Constants.ModeInfoSizeLog2;
// Current libaom excludes the rightmost column from this conformance check because it receives the // The reference decoder excludes the rightmost column from this conformance check because it receives the
// remainder of the coded width. Every inner column must be at least 64 pixels, doubled when the frame // remainder of the coded width. Every inner column must be at least 64 pixels, doubled when the frame
// is super-resolution scaled. // is super-resolution scaled.
if (tileWidth < minimumInnerTileWidth) if (tileWidth < minimumInnerTileWidth)
@ -1811,7 +1811,7 @@ internal class ObuReader
// Slot occupancy and frame-ID validity are independent normative states. Short signaling derives roles // Slot occupancy and frame-ID validity are independent normative states. Short signaling derives roles
// from every occupied slot before this per-role validity check, matching av1_set_frame_refs followed by // from every occupied slot before this per-role validity check, matching av1_set_frame_refs followed by
// libaom's valid_for_referencing check. // the reference decoder's valid_for_referencing check.
if (referenceFrames.Resolve((int)slot) is null) if (referenceFrames.Resolve((int)slot) is null)
{ {
throw new InvalidImageContentException("An AV1 inter frame selects an unoccupied reference-map slot."); throw new InvalidImageContentException("An AV1 inter frame selects an unoccupied reference-map slot.");

2
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSkipModeParameters.cs

@ -54,7 +54,7 @@ internal class ObuSkipModeParameters
int nearestBackwardReferenceIndex = -1; int nearestBackwardReferenceIndex = -1;
// The seven entries are canonical roles, while each value selects one physical reference-map slot. Compare // The seven entries are canonical roles, while each value selects one physical reference-map slot. Compare
// the selected slot's order hint so duplicate roles retain the same deterministic ordering as libaom. // the selected slot's order hint so duplicate roles retain the same deterministic ordering as the reference decoder.
for (int referenceIndex = 0; referenceIndex < Av1Constants.ReferencesPerFrame; referenceIndex++) for (int referenceIndex = 0; referenceIndex < Av1Constants.ReferencesPerFrame; referenceIndex++)
{ {
uint referenceOrderHint = referenceOrderHints[(int)referenceFrameIndices[referenceIndex]]; uint referenceOrderHint = referenceOrderHints[(int)referenceFrameIndices[referenceIndex]];

2
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs

@ -523,7 +523,7 @@ internal class ObuWriter
int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
if (tileCount > 1) if (tileCount > 1)
{ {
// A combined OBU_FRAME has implicit complete-frame tile bounds. Current libaom still // A combined OBU_FRAME has implicit complete-frame tile bounds. The reference decoder still
// writes the presence bit for a multi-tile frame, but requires that bit to remain zero. // writes the presence bit for a multi-tile frame, but requires that bit to remain zero.
writer.WriteBoolean(false); writer.WriteBoolean(false);
} }

68
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs

@ -103,16 +103,10 @@ internal sealed class Av1FrameDecoder : IAv1FrameDecoder, IDisposable
public void Dispose() => this.blockDecoder.Dispose(); public void Dispose() => this.blockDecoder.Dispose();
/// <summary> /// <summary>
/// Reconstructs every coded tile and applies the implemented in-loop frame stages in normative order. /// Applies the in-loop frame stages after every superblock has been reconstructed.
/// </summary> /// </summary>
public void DecodeFrame() public void CompleteFrame()
{ {
// Tile columns are the outer traversal because each call walks that column's tile rows and their superblocks.
for (int column = 0; column < this.frameHeader.TilesInfo.TileColumnCount; column++)
{
this.DecodeFrameTiles(column);
}
bool doLoopRestoration = this.frameHeader.LoopRestorationParameters.UsesLoopRestoration; bool doLoopRestoration = this.frameHeader.LoopRestorationParameters.UsesLoopRestoration;
Av1LoopFilterDecoder loopFilterDecoder = new( Av1LoopFilterDecoder loopFilterDecoder = new(
@ -156,61 +150,12 @@ internal sealed class Av1FrameDecoder : IAv1FrameDecoder, IDisposable
// The owning decoder applies grain only to the presentation buffer after reference ownership is established. // The owning decoder applies grain only to the presentation buffer after reference ownership is established.
} }
/// <summary>
/// Reconstructs every tile row in one tile column.
/// </summary>
/// <param name="tileColumn">The zero-based tile-column index.</param>
/// <remarks>Follows libaom's single-threaded <c>decode_tiles</c> ordering.</remarks>
private void DecodeFrameTiles(int tileColumn)
{
ObuTileGroupHeader tileInfo = this.frameHeader.TilesInfo;
for (int tileRow = 0; tileRow < tileInfo.TileRowCount; tileRow++)
{
// Tile boundaries are expressed in 4x4 mode-info units. Walk every superblock row between consecutive
// boundaries; using only the tile-row index would reconstruct one row and leave taller tiles incomplete.
int modeInfoRowStart = tileInfo.TileRowStartModeInfo[tileRow];
int modeInfoRowEnd = tileInfo.TileRowStartModeInfo[tileRow + 1];
for (int modeInfoRow = modeInfoRowStart;
modeInfoRow < modeInfoRowEnd;
modeInfoRow += this.sequenceHeader.SuperblockModeInfoSize)
{
int superblockRow = modeInfoRow / this.sequenceHeader.SuperblockModeInfoSize;
this.DecodeTileSuperblockRow(tileRow, tileColumn, modeInfoRow, superblockRow);
}
}
}
/// <summary>
/// Reconstructs one superblock row within a tile from left to right.
/// </summary>
/// <param name="tileRow">The zero-based tile-row index.</param>
/// <param name="tileColumn">The zero-based tile-column index.</param>
/// <param name="modeInfoRow">The frame-relative row in 4x4 mode-info units.</param>
/// <param name="superblockRow">The frame-relative superblock row.</param>
/// <remarks>Corresponds to the superblock-row traversal in libaom's <c>decode_tile</c>.</remarks>
private void DecodeTileSuperblockRow(int tileRow, int tileColumn, int modeInfoRow, int superblockRow)
{
ObuTileGroupHeader tileInfo = this.frameHeader.TilesInfo;
for (int modeInfoColumn = tileInfo.TileColumnStartModeInfo[tileColumn]; modeInfoColumn < tileInfo.TileColumnStartModeInfo[tileColumn + 1];
modeInfoColumn += this.sequenceHeader.SuperblockModeInfoSize)
{
// Convert the signaled 4x4 mode-info column to the frame-level superblock index used by Av1FrameInfo.
int superblockColumn = modeInfoColumn << Av1Constants.ModeInfoSizeLog2 >> this.sequenceHeader.SuperblockSizeLog2;
Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(new Point(superblockColumn, superblockRow));
Point modeInfoPosition = new(modeInfoColumn, modeInfoRow);
this.DecodeSuperblock(modeInfoPosition, superblockInfo, new Av1TileInfo(tileRow, tileColumn, this.frameHeader));
}
}
/// <summary> /// <summary>
/// Reconstructs one superblock after applying its block state and delta-Q context. /// Reconstructs one superblock after applying its block state and delta-Q context.
/// </summary> /// </summary>
/// <param name="modeInfoPosition">The superblock's top-left position in 4x4 mode-info units.</param> /// <param name="modeInfoPosition">The superblock's top-left position in 4x4 mode-info units.</param>
/// <param name="superblockInfo">The decoded syntax and block modes for the superblock.</param> /// <param name="superblockInfo">The decoded syntax and block modes for the superblock.</param>
/// <param name="tileInfo">The tile that contains the superblock.</param> /// <param name="tileInfo">The tile that contains the superblock.</param>
/// <remarks>Corresponds to libaom's superblock decode boundary.</remarks>
public void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) public void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
{ {
this.blockDecoder.UpdateSuperblock(superblockInfo); this.blockDecoder.UpdateSuperblock(superblockInfo);
@ -224,10 +169,10 @@ internal sealed class Av1FrameDecoder : IAv1FrameDecoder, IDisposable
/// <param name="modeInfoPosition">The superblock's frame-relative origin in 4x4 mode-info units.</param> /// <param name="modeInfoPosition">The superblock's frame-relative origin in 4x4 mode-info units.</param>
/// <param name="superblockInfo">The superblock whose block modes are traversed.</param> /// <param name="superblockInfo">The superblock whose block modes are traversed.</param>
/// <param name="tileInfo">The tile boundary information used by intra prediction.</param> /// <param name="tileInfo">The tile boundary information used by intra prediction.</param>
/// <remarks>Replays the depth-first block order produced by libaom's <c>decode_partition</c>.</remarks> /// <remarks>Traverses the depth-first block order produced by tile parsing.</remarks>
private void DecodePartition(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) private void DecodePartition(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
{ {
foreach (Av1BlockModeInfo modeInfo in superblockInfo.GetModeInfos()) foreach (ref Av1BlockModeInfo modeInfo in superblockInfo.GetModeInfos())
{ {
Point subPosition = modeInfo.PositionInSuperblock; Point subPosition = modeInfo.PositionInSuperblock;
Av1BlockSize subSize = modeInfo.BlockSize; Av1BlockSize subSize = modeInfo.BlockSize;
@ -236,6 +181,11 @@ internal sealed class Av1FrameDecoder : IAv1FrameDecoder, IDisposable
// Block positions are stored relative to the superblock; prediction and reconstruction require frame-relative mode-info coordinates. // Block positions are stored relative to the superblock; prediction and reconstruction require frame-relative mode-info coordinates.
globalPosition.Offset(subPosition); globalPosition.Offset(subPosition);
this.blockDecoder.DecodeBlock(modeInfo, globalPosition, subSize, superblockInfo, tileInfo); this.blockDecoder.DecodeBlock(modeInfo, globalPosition, subSize, superblockInfo, tileInfo);
// Palette maps are decoder-session scratch. Retained mode information must not keep views after the block
// has consumed them because the next superblock reuses the same storage.
modeInfo.SetPaletteColorIndexMap(Av1PlaneType.Y, default);
modeInfo.SetPaletteColorIndexMap(Av1PlaneType.Uv, default);
} }
} }
} }

3
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs

@ -27,10 +27,9 @@ internal class Av1FrameEncoder
/// <summary> /// <summary>
/// Represents the not-yet-implemented entry point for encoding one AV1 still-image frame. /// Represents the not-yet-implemented entry point for encoding one AV1 still-image frame.
/// </summary> /// </summary>
/// <remarks>SVT-AV1: <c>svt_av1_enc_init</c>.</remarks>
public static void Encode() public static void Encode()
{ {
// Still-image encoding needs the normative analysis, transform, quantization, entropy, and packetization stages, // Still-image encoding needs the normative analysis, transform, quantization, entropy, and packetization stages,
// but it does not require SVT-AV1's application-level worker graph or video-sequence process orchestration. // but it does not require the encoder's application-level worker graph or video-sequence process orchestration.
} }
} }

2
src/ImageSharp/Formats/Heif/Av1/Pipeline/Cdef/Av1CdefDecoder.cs

@ -257,7 +257,7 @@ internal class Av1CdefDecoder
{ {
int blockIndex = 0; int blockIndex = 0;
// libaom analyzes two listed 8x8 blocks together. The per-unit fixed list preserves that traversal // the reference decoder analyzes two listed 8x8 blocks together. The per-unit fixed list preserves that traversal
// without allocating a managed block list or repeating four skip-map lookups during filtering. // without allocating a managed block list or repeating four skip-map lookups during filtering.
for (; blockIndex < blockCount - 1; blockIndex += 2) for (; blockIndex < blockCount - 1; blockIndex += 2)
{ {

14
src/ImageSharp/Formats/Heif/Av1/Pipeline/Cdef/Av1CdefFilter.cs

@ -52,7 +52,7 @@ internal static partial class Av1CdefFilter
if (Avx2.IsSupported) if (Avx2.IsSupported)
{ {
// AV1 plane dimensions are multiples of four, so the CDEF copy has an even row count. Processing two rows // AV1 plane dimensions are multiples of four, so the CDEF copy has an even row count. Processing two rows
// together follows libaom's AVX2 scheduling while each conversion widens sixteen unsigned samples exactly. // together follows the reference decoder's AVX2 scheduling while each conversion widens sixteen unsigned samples exactly.
for (int row = 0; row < height; row += 2) for (int row = 0; row < height; row += 2)
{ {
int firstSourceRow = sourceOffset + (row * sourceStride); int firstSourceRow = sourceOffset + (row * sourceStride);
@ -363,7 +363,7 @@ internal static partial class Av1CdefFilter
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); ref ushort sourceBase = ref MemoryMarshal.GetReference(source);
ref TSample destinationBase = ref MemoryMarshal.GetReference(destination); ref TSample destinationBase = ref MemoryMarshal.GetReference(destination);
// libaom selects one of four closed kernels from the two strength flags. The semantic operator makes the same // the reference decoder selects one of four closed kernels from the two strength flags. The semantic operator makes the same
// choice once per block so the JIT removes primary/secondary mode branches from every row and tap. // choice once per block so the JIT removes primary/secondary mode branches from every row and tap.
if (primaryStrength != 0) if (primaryStrength != 0)
{ {
@ -602,7 +602,7 @@ internal static partial class Av1CdefFilter
Vector256<short> one = Vector256.Create((short)1); Vector256<short> one = Vector256.Create((short)1);
int rowsPerBatch = blockWidth == 8 ? 2 : 4; int rowsPerBatch = blockWidth == 8 ? 2 : 4;
// The 256-bit lane layout follows libaom: two complete 8-wide rows, or four complete 4-wide rows. Directional // The 256-bit lane layout follows the reference decoder: two complete 8-wide rows, or four complete 4-wide rows. Directional
// offsets therefore remain ordinary source offsets, while all constrain, weight, clip, and round operations // offsets therefore remain ordinary source offsets, while all constrain, weight, clip, and round operations
// advance several output rows together without crossing a row boundary inside any 128-bit lane. // advance several output rows together without crossing a row boundary inside any 128-bit lane.
for (int row = 0; row < blockHeight; row += rowsPerBatch) for (int row = 0; row < blockHeight; row += rowsPerBatch)
@ -702,7 +702,7 @@ internal static partial class Av1CdefFilter
lines[row] = (samples >> coefficientShift).AsInt16() - analysisBias; lines[row] = (samples >> coefficientShift).AsInt16() - analysisBias;
} }
// Vector lanes are written in memory order. These are the low-to-high forms of libaom's set-style constants. // Vector lanes are written in memory order. These are the low-to-high forms of the reference decoder's set-style constants.
Vector128<int> foldWeights0 = Vector128.Create(840, 420, 280, 210); Vector128<int> foldWeights0 = Vector128.Create(840, 420, 280, 210);
Vector128<int> foldWeights1 = Vector128.Create(168, 140, 120, 105); Vector128<int> foldWeights1 = Vector128.Create(168, 140, 120, 105);
Vector128<int> diagonalWeights0 = Vector128.Create(0, 0, 420, 210); Vector128<int> diagonalWeights0 = Vector128.Create(0, 0, 420, 210);
@ -711,7 +711,7 @@ internal static partial class Av1CdefFilter
ref int costBase = ref costs[0]; ref int costBase = ref costs[0];
// The first pass evaluates directions 4..7. Rotating the block counter-clockwise lets the identical arithmetic // The first pass evaluates directions 4..7. Rotating the block counter-clockwise lets the identical arithmetic
// evaluate directions 0..3, exactly matching libaom's portable vector implementation. // evaluate directions 0..3, exactly matching the reference decoder's portable vector implementation.
ComputeDirectionCosts(ref lines, foldWeights0, foldWeights1, diagonalWeights0, diagonalWeights1).StoreUnsafe(ref costBase, 4); ComputeDirectionCosts(ref lines, foldWeights0, foldWeights1, diagonalWeights0, diagonalWeights1).StoreUnsafe(ref costBase, 4);
ReverseTranspose(ref lines); ReverseTranspose(ref lines);
ComputeDirectionCosts(ref lines, foldWeights0, foldWeights1, diagonalWeights0, diagonalWeights1).StoreUnsafe(ref costBase); ComputeDirectionCosts(ref lines, foldWeights0, foldWeights1, diagonalWeights0, diagonalWeights1).StoreUnsafe(ref costBase);
@ -751,7 +751,7 @@ internal static partial class Av1CdefFilter
Vector128<ushort> first = Vector128.LoadUnsafe(ref sourceBase, (nuint)(firstSourceOffset + (row * sourceStride))); Vector128<ushort> first = Vector128.LoadUnsafe(ref sourceBase, (nuint)(firstSourceOffset + (row * sourceStride)));
Vector128<ushort> second = Vector128.LoadUnsafe(ref sourceBase, (nuint)(secondSourceOffset + (row * sourceStride))); Vector128<ushort> second = Vector128.LoadUnsafe(ref sourceBase, (nuint)(secondSourceOffset + (row * sourceStride)));
// Keeping one block in each 128-bit lane is the critical libaom layout: every byte shift, unpack, multiply, // Keeping one block in each 128-bit lane is critical: every byte shift, unpack, multiply,
// and transpose remains lane-local while one AVX2 instruction advances both direction searches. // and transpose remains lane-local while one AVX2 instruction advances both direction searches.
lines[row] = (Vector256.Create(first, second) >> coefficientShift).AsInt16() - analysisBias; lines[row] = (Vector256.Create(first, second) >> coefficientShift).AsInt16() - analysisBias;
} }
@ -1548,7 +1548,7 @@ internal static partial class Av1CdefFilter
} }
// Four-row batches use one 64-bit load per row. Pairing two rows in each 128-bit lane preserves the exact row // Four-row batches use one 64-bit load per row. Pairing two rows in each 128-bit lane preserves the exact row
// boundaries required by libaom's lane-local shifts while avoiding reads beyond the frame sentinel border. // boundaries required by the reference decoder's lane-local shifts while avoiding reads beyond the frame sentinel border.
Vector64<short> row0 = LoadSamples(ref source, offset, 4).GetLower(); Vector64<short> row0 = LoadSamples(ref source, offset, 4).GetLower();
Vector64<short> row1 = LoadSamples(ref source, offset + stride, 4).GetLower(); Vector64<short> row1 = LoadSamples(ref source, offset + stride, 4).GetLower();
Vector64<short> row2 = LoadSamples(ref source, offset + (2 * stride), 4).GetLower(); Vector64<short> row2 = LoadSamples(ref source, offset + (2 * stride), 4).GetLower();

2
src/ImageSharp/Formats/Heif/Av1/Pipeline/FilmGrain/Av1FilmGrainDecoder.cs

@ -359,7 +359,7 @@ internal sealed class Av1FilmGrainDecoder
? scratch.Slice(scratchOffset, chromaColumnLength) ? scratch.Slice(scratchOffset, chromaColumnLength)
: Span<int>.Empty; : Span<int>.Empty;
// libaom zero-initializes the lookup structure before expanding control points. This matters // the reference decoder zero-initializes the lookup structure before expanding control points. This matters
// when chroma scaling is inherited from an empty luma scaling function. // when chroma scaling is inherited from an empty luma scaling function.
scalingY.Clear(); scalingY.Clear();
scalingCb.Clear(); scalingCb.Clear();

4
src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1DeblockingFilter.cs

@ -481,7 +481,7 @@ internal static partial class Av1DeblockingFilter
Vector128<int> signedQ0 = q0 - offset; Vector128<int> signedQ0 = q0 - offset;
Vector128<int> signedQ1 = q1 - offset; Vector128<int> signedQ1 = q1 - offset;
// libaom performs every delta operation in the signed sample domain. Saturating only the final samples is // the reference decoder performs every delta operation in the signed sample domain. Saturating only the final samples is
// not equivalent because the intermediate delta can clip before the asymmetric +4/+3 rounding is applied. // not equivalent because the intermediate delta can clip before the asymmetric +4/+3 rounding is applied.
Vector128<int> filter = Vector128.ConditionalSelect(highEdgeVariance, Vector128.Clamp(signedP1 - signedQ1, minimum, maximum), Vector128<int>.Zero); Vector128<int> filter = Vector128.ConditionalSelect(highEdgeVariance, Vector128.Clamp(signedP1 - signedQ1, minimum, maximum), Vector128<int>.Zero);
filter = Vector128.Clamp(filter + (3 * (signedQ0 - signedP0)), minimum, maximum) & filterEnabled; filter = Vector128.Clamp(filter + (3 * (signedQ0 - signedP0)), minimum, maximum) & filterEnabled;
@ -614,7 +614,7 @@ internal static partial class Av1DeblockingFilter
Vector128<int> wideFilter = filterEnabled & flat & outerFlat; Vector128<int> wideFilter = filterEnabled & flat & outerFlat;
// The eight-tap routine first produces the normative fallback. Lanes satisfying the outer flatness mask are // The eight-tap routine first produces the normative fallback. Lanes satisfying the outer flatness mask are
// then replaced with the wider results, matching libaom's mask blend without evaluating lanes independently. // then replaced with the wider results, matching the reference decoder's mask blend without evaluating lanes independently.
Filter8(ref samples, filterEnabled, flat, highEdgeVarianceThreshold, bitDepth); Filter8(ref samples, filterEnabled, flat, highEdgeVarianceThreshold, bitDepth);
if (Vector128.EqualsAll(wideFilter, Vector128<int>.Zero)) if (Vector128.EqualsAll(wideFilter, Vector128<int>.Zero))
{ {

10
src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs

@ -75,7 +75,7 @@ internal class Av1LoopFilterDecoder
ObuColorConfig colorConfig = this.sequenceHeader.ColorConfig; ObuColorConfig colorConfig = this.sequenceHeader.ColorConfig;
int modeInfoRowsPerBand = 1 << (Av1Constants.MaxSuperBlockSizeLog2 - Av1Constants.ModeInfoSizeLog2); int modeInfoRowsPerBand = 1 << (Av1Constants.MaxSuperBlockSizeLog2 - Av1Constants.ModeInfoSizeLog2);
// libaom's loop_filter_rows processes one MAX_MIB_SIZE band at a time so that a completed band can be // the reference decoder's loop_filter_rows processes one MAX_MIB_SIZE band at a time so that a completed band can be
// presented before the remainder of the frame. The ordering is observable because the vertical and // presented before the remainder of the frame. The ordering is observable because the vertical and
// horizontal passes modify intersecting sample neighborhoods in place. // horizontal passes modify intersecting sample neighborhoods in place.
for (int rowStart = 0; rowStart < this.frameHeader.ModeInfoRowCount; rowStart += modeInfoRowsPerBand) for (int rowStart = 0; rowStart < this.frameHeader.ModeInfoRowCount; rowStart += modeInfoRowsPerBand)
@ -162,7 +162,7 @@ internal class Av1LoopFilterDecoder
} }
// Horizontal edges are visited down each column. This ordering is observable because adjacent horizontal // Horizontal edges are visited down each column. This ordering is observable because adjacent horizontal
// filters can modify samples that a later edge reads, so it must match libaom's av1_filter_block_plane_horz. // filters can modify samples that a later edge reads, so it must match the reference decoder's av1_filter_block_plane_horz.
for (int column = 0; column < this.frameHeader.ModeInfoColumnCount; column += columnStep) for (int column = 0; column < this.frameHeader.ModeInfoColumnCount; column += columnStep)
{ {
for (int row = rowStart; row < rowEnd; row += rowStep) for (int row = rowStart; row < rowEnd; row += rowStep)
@ -233,12 +233,12 @@ internal class Av1LoopFilterDecoder
: planeY % transformSize.GetHeight() == 0; : planeY % transformSize.GetHeight() == 0;
// A skipped intra block still has reconstructed prediction samples and is not a skipped inter transform. // A skipped intra block still has reconstructed prediction samples and is not a skipped inter transform.
// libaom therefore applies the skip predicate only when the corresponding primary reference is inter. // The skip predicate therefore applies only when the corresponding primary reference is inter.
bool currentSkippedTransform = modeInfo.Skip && modeInfo.ReferenceFrames[0] > Av1ReferenceFrameType.Intra; bool currentSkippedTransform = modeInfo.Skip && modeInfo.ReferenceFrames[0] > Av1ReferenceFrameType.Intra;
bool previousSkippedTransform = previousModeInfo.Skip && previousModeInfo.ReferenceFrames[0] > Av1ReferenceFrameType.Intra; bool previousSkippedTransform = previousModeInfo.Skip && previousModeInfo.ReferenceFrames[0] > Av1ReferenceFrameType.Intra;
// Every covered 4x4 position carries the owning block's storage index. Comparing those indices is the value-type // Every covered 4x4 position carries the owning block's storage index. Comparing those indices is the value-type
// equivalent of libaom's current-versus-previous MB_MODE_INFO pointer comparison at a prediction-unit boundary. // equivalent of the reference decoder's current-versus-previous MB_MODE_INFO pointer comparison at a prediction-unit boundary.
bool isBlockEdge = modeInfo.ModeInfoIndex != previousModeInfo.ModeInfoIndex; bool isBlockEdge = modeInfo.ModeInfoIndex != previousModeInfo.ModeInfoIndex;
bool applyFilter = isTransformEdge && (isBlockEdge || !currentSkippedTransform || !previousSkippedTransform); bool applyFilter = isTransformEdge && (isBlockEdge || !currentSkippedTransform || !previousSkippedTransform);
if (!applyFilter) if (!applyFilter)
@ -380,7 +380,7 @@ internal class Av1LoopFilterDecoder
if (referenceFrame > Av1ReferenceFrameType.Intra) if (referenceFrame > Av1ReferenceFrameType.Intra)
{ {
// AV1's second mode-delta class contains every inter mode except the two global-motion modes. // AV1's second mode-delta class contains every inter mode except the two global-motion modes.
// Keeping this classification next to the level arithmetic mirrors libaom's mode_lf_lut lookup. // Keeping this classification next to the level arithmetic mirrors the reference decoder's mode_lf_lut lookup.
int modeDeltaIndex = modeInfo.YMode is Av1PredictionMode.GlobalMotionVector or int modeDeltaIndex = modeInfo.YMode is Av1PredictionMode.GlobalMotionVector or
Av1PredictionMode.GlobalGlobalMotionVector ? 0 : 1; Av1PredictionMode.GlobalGlobalMotionVector ? 0 : 1;

2
src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopRestoration/Av1SelfGuidedFilter.Operations.cs

@ -256,7 +256,7 @@ internal static partial class Av1SelfGuidedFilter
for (; column <= vectorEnd; column += Vector256<int>.Count) for (; column <= vectorEnd; column += Vector256<int>.Count)
{ {
// Eight packed 16-bit samples become eight 32-bit lanes. The prefix scans mirror // Eight packed 16-bit samples become eight 32-bit lanes. The prefix scans mirror
// libaom's scan_32, and the replicated carry joins consecutive vector batches. // the reference decoder's scan_32, and the replicated carry joins consecutive vector batches.
Vector128<ushort> packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(sourceRowOffset + column)); Vector128<ushort> packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(sourceRowOffset + column));
Vector256<int> samples = Vector256.WidenLower(Vector256.Create(packed, Vector128<ushort>.Zero)).AsInt32(); Vector256<int> samples = Vector256.WidenLower(Vector256.Create(packed, Vector128<ushort>.Zero)).AsInt32();
Vector256<int> squares = samples * samples; Vector256<int> squares = samples * samples;

2
src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopRestoration/Av1WienerFilter.cs

@ -151,7 +151,7 @@ internal static class Av1WienerFilter
filter[5] = (short)middle; filter[5] = (short)middle;
filter[6] = (short)outer; filter[6] = (short)outer;
// libaom stores a seven-tap Wiener kernel in the shared eight-tap interpolation shape. // the reference decoder stores a seven-tap Wiener kernel in the shared eight-tap interpolation shape.
filter[7] = 0; filter[7] = 0;
} }

1
src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1DeQuantizationContext.cs

@ -26,7 +26,6 @@ internal class Av1DeQuantizationContext
/// </summary> /// </summary>
/// <param name="sequenceHeader">The sequence header that supplies the coded bit depth.</param> /// <param name="sequenceHeader">The sequence header that supplies the coded bit depth.</param>
/// <param name="frameHeader">The frame header that supplies segmentation and quantization parameters.</param> /// <param name="frameHeader">The frame header that supplies segmentation and quantization parameters.</param>
/// <remarks>Corresponds to <c>setup_segmentation_dequant</c> in libaom.</remarks>
public Av1DeQuantizationContext(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) public Av1DeQuantizationContext(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
Av1BitDepth bitDepth = sequenceHeader.ColorConfig.BitDepth; Av1BitDepth bitDepth = sequenceHeader.ColorConfig.BitDepth;

2
src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1InverseQuantizer.cs

@ -80,7 +80,6 @@ internal class Av1InverseQuantizer
/// <param name="transformSize">The transform dimensions and scale.</param> /// <param name="transformSize">The transform dimensions and scale.</param>
/// <param name="plane">The color plane whose quantizer and matrix are used.</param> /// <param name="plane">The color plane whose quantizer and matrix are used.</param>
/// <returns>The number of coefficient levels consumed.</returns> /// <returns>The number of coefficient levels consumed.</returns>
/// <remarks>Matches the coefficient dequantization arithmetic in libaom's <c>read_coeffs_txb</c>.</remarks>
public int InverseQuantize(Av1BlockModeInfo mode, Span<int> level, Span<int> qCoefficients, Av1TransformType transformType, Av1TransformSize transformSize, Av1Plane plane) public int InverseQuantize(Av1BlockModeInfo mode, Span<int> level, Span<int> qCoefficients, Av1TransformType transformType, Av1TransformSize transformSize, Av1Plane plane)
{ {
Guard.NotNull(this.deQuantsDeltaQ); Guard.NotNull(this.deQuantsDeltaQ);
@ -159,7 +158,6 @@ internal class Av1InverseQuantizer
/// <param name="coefficientIndex">The raster coefficient index into the inverse matrix.</param> /// <param name="coefficientIndex">The raster coefficient index into the inverse matrix.</param>
/// <param name="iqMatrix">The inverse quantization matrix for the current level, plane, and transform size.</param> /// <param name="iqMatrix">The inverse quantization matrix for the current level, plane, and transform size.</param>
/// <returns>The matrix-weighted dequantization value.</returns> /// <returns>The matrix-weighted dequantization value.</returns>
/// <remarks>Corresponds to <c>get_dqv</c> in libaom.</remarks>
private static int GetDeQuantizedValue(short dequant, int coefficientIndex, ReadOnlySpan<int> iqMatrix) private static int GetDeQuantizedValue(short dequant, int coefficientIndex, ReadOnlySpan<int> iqMatrix)
{ {
// Matrix elements use fixed-point precision; adding half a unit produces nearest-integer rounding on shift. // Matrix elements use fixed-point precision; adding half a unit produces nearest-integer rounding on shift.

2
src/ImageSharp/Formats/Heif/Av1/Pipeline/SuperResolution/Av1SuperResolutionDecoder.cs

@ -125,7 +125,7 @@ internal class Av1SuperResolutionDecoder
int sourceStart = Av1SuperResolutionFilter.SourceBorder; int sourceStart = Av1SuperResolutionFilter.SourceBorder;
int bitDepth = this.frameBuffer.BitDepth.GetBitCount(); int bitDepth = this.frameBuffer.BitDepth.GetBitCount();
// libaom partitions the same continuous phase progression by tile column but does not pad // the reference decoder partitions the same continuous phase progression by tile column but does not pad
// internal boundaries. Filtering the complete row therefore produces the identical samples. // internal boundaries. Filtering the complete row therefore produces the identical samples.
for (int row = 0; row < height; row++) for (int row = 0; row < height; row++)
{ {

2
src/ImageSharp/Formats/Heif/Av1/Pipeline/SuperResolution/Av1SuperResolutionFilter.cs

@ -327,7 +327,7 @@ internal static class Av1SuperResolutionFilter
Vector128<int> products2 = Vector128_.MultiplyAddAdjacent(samples2, filter2); Vector128<int> products2 = Vector128_.MultiplyAddAdjacent(samples2, filter2);
Vector128<int> products3 = Vector128_.MultiplyAddAdjacent(samples3, filter3); Vector128<int> products3 = Vector128_.MultiplyAddAdjacent(samples3, filter3);
// libaom reduces four independent filters in two horizontal-add stages so the four complete sums occupy // the reference decoder reduces four independent filters in two horizontal-add stages so the four complete sums occupy
// consecutive lanes. Keeping that arrangement also allows both destination forms to use one packed store. // consecutive lanes. Keeping that arrangement also allows both destination forms to use one packed store.
Vector128<int> pairs01 = HorizontalAdd(products0, products1); Vector128<int> pairs01 = HorizontalAdd(products0, products1);
Vector128<int> pairs23 = HorizontalAdd(products2, products3); Vector128<int> pairs23 = HorizontalAdd(products2, products3);

2
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1ChromaPredictionModeExtensions.cs

@ -30,7 +30,7 @@ internal static class Av1ChromaPredictionModeExtensions
Av1ChromaPredictionMode.SmoothHorizontal => Av1PredictionMode.SmoothHorizontal, Av1ChromaPredictionMode.SmoothHorizontal => Av1PredictionMode.SmoothHorizontal,
Av1ChromaPredictionMode.Paeth => Av1PredictionMode.Paeth, Av1ChromaPredictionMode.Paeth => Av1PredictionMode.Paeth,
// Chroma-from-luma adds its AC contribution to a DC prediction. libaom's get_uv_mode() therefore maps it // Chroma-from-luma adds its AC contribution to a DC prediction. the reference decoder's get_uv_mode() therefore maps it
// to DC when shared transform and neighbor metadata require the corresponding luma predictor. // to DC when shared transform and neighbor metadata require the corresponding luma predictor.
Av1ChromaPredictionMode.ChromaFromLuma => Av1PredictionMode.DC, Av1ChromaPredictionMode.ChromaFromLuma => Av1PredictionMode.DC,
_ => Av1PredictionMode.IntraInvalid, _ => Av1PredictionMode.IntraInvalid,

2
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.Operations.cs

@ -566,7 +566,7 @@ internal static partial class Av1DirectionalIntraPredictor
} }
else if (angle is > 180 and < 270) else if (angle is > 180 and < 270)
{ {
// libaom computes zone 3 as a zone 1 block with swapped dimensions, then transposes it. This preserves // the reference decoder computes zone 3 as a zone 1 block with swapped dimensions, then transposes it. This preserves
// contiguous reference reads and destination stores in both hot stages instead of scattering columns. // contiguous reference reads and destination stores in both hot stages instead of scattering columns.
Span<byte> transposed = scratch[..(width * height)]; Span<byte> transposed = scratch[..(width * height)];
PredictZone1(transposed, height, left, upsampleLeft, GetDeltaY(angle), height, width); PredictZone1(transposed, height, left, upsampleLeft, GetDeltaY(angle), height, width);

10
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs

@ -85,7 +85,6 @@ internal class Av1PredictionDecoder
/// <param name="bitDepth">The bit depth of the reconstructed samples.</param> /// <param name="bitDepth">The bit depth of the reconstructed samples.</param>
/// <param name="blockModeInfoColumnOffset">The transform block's horizontal offset within the mode-information block.</param> /// <param name="blockModeInfoColumnOffset">The transform block's horizontal offset within the mode-information block.</param>
/// <param name="blockModeInfoRowOffset">The transform block's vertical offset within the mode-information block.</param> /// <param name="blockModeInfoRowOffset">The transform block's vertical offset within the mode-information block.</param>
/// <remarks>Corresponds to <c>svt_av1_predict_intra</c> in SVT-AV1.</remarks>
public void Decode( public void Decode(
ref Av1PartitionInfo partitionInfo, ref Av1PartitionInfo partitionInfo,
Av1Plane plane, Av1Plane plane,
@ -181,7 +180,7 @@ internal class Av1PredictionDecoder
Span<T> topNeighbor = referenceBuffer; Span<T> topNeighbor = referenceBuffer;
ReadOnlySpan<T> leftNeighbor = referenceBuffer[(referenceStride - 1)..]; ReadOnlySpan<T> leftNeighbor = referenceBuffer[(referenceStride - 1)..];
// Libaom predicts one maximum-transform-sized plane block for inter-intra. Destination storage is separate // The reference decoder 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. // because the inter predictor must remain intact until the final mask blend consumes both complete blocks.
this.PredictIntraBlock( this.PredictIntraBlock(
ref partitionInfo, ref partitionInfo,
@ -260,7 +259,7 @@ internal class Av1PredictionDecoder
{ {
int stride = pixelStride; int stride = pixelStride;
// Unlike SVT's separate destination and reference pointers, this span begins at the // Unlike the encoder's separate destination and reference pointers, this span begins at the
// previous row. That layout exposes the top, top-left, and strided left samples without copying. // previous row. That layout exposes the top, top-left, and strided left samples without copying.
Span<T> topNeighbor = pixelBuffer; Span<T> topNeighbor = pixelBuffer;
Span<T> leftNeighbor = pixelBuffer[(stride - 1)..]; Span<T> leftNeighbor = pixelBuffer[(stride - 1)..];
@ -298,7 +297,7 @@ internal class Av1PredictionDecoder
if (plane != Av1Plane.Y) if (plane != Av1Plane.Y)
{ {
// Chroma and luma modes are separate bitstream domains. Shared spatial predictors consume the explicit // Chroma and luma modes are separate bitstream domains. Shared spatial predictors consume the explicit
// libaom get_uv_mode() equivalent rather than relying on their matching ordinal values. // the reference decoder get_uv_mode() equivalent rather than relying on their matching ordinal values.
mode = partitionInfo.ModeInfo.UvMode.ToLumaMode(); mode = partitionInfo.ModeInfo.UvMode.ToLumaMode();
} }
@ -1539,7 +1538,6 @@ internal class Av1PredictionDecoder
/// <param name="delta">The prediction angle relative to the edge's cardinal direction.</param> /// <param name="delta">The prediction angle relative to the edge's cardinal direction.</param>
/// <param name="filterType">A value indicating whether a neighboring smooth mode selects the alternate thresholds.</param> /// <param name="filterType">A value indicating whether a neighboring smooth mode selects the alternate thresholds.</param>
/// <returns><see langword="true"/> when the edge must be upsampled; otherwise, <see langword="false"/>.</returns> /// <returns><see langword="true"/> when the edge must be upsampled; otherwise, <see langword="false"/>.</returns>
/// <remarks>Corresponds to <c>svt_aom_use_intra_edge_upsample</c> in SVT-AV1.</remarks>
private static bool UseIntraEdgeUpsample(int width, int height, int delta, bool filterType) private static bool UseIntraEdgeUpsample(int width, int height, int delta, bool filterType)
{ {
int d = Math.Abs(delta); int d = Math.Abs(delta);
@ -1560,7 +1558,6 @@ internal class Av1PredictionDecoder
/// <param name="count">The number of edge samples.</param> /// <param name="count">The number of edge samples.</param>
/// <param name="strength">The AV1 filter-strength index from zero through three.</param> /// <param name="strength">The AV1 filter-strength index from zero through three.</param>
/// <param name="scratch">The reusable padded source workspace.</param> /// <param name="scratch">The reusable padded source workspace.</param>
/// <remarks>Corresponds to <c>svt_av1_filter_intra_edge_c</c> in SVT-AV1.</remarks>
private static void FilterIntraEdge<T>(ref T buffer, int count, int strength, Span<T> scratch) private static void FilterIntraEdge<T>(ref T buffer, int count, int strength, Span<T> scratch)
where T : unmanaged, IBinaryInteger<T> where T : unmanaged, IBinaryInteger<T>
{ {
@ -1825,7 +1822,6 @@ internal class Av1PredictionDecoder
/// <param name="delta">The prediction angle relative to the edge's cardinal direction.</param> /// <param name="delta">The prediction angle relative to the edge's cardinal direction.</param>
/// <param name="filterType">A value indicating whether a neighboring smooth mode selects the alternate thresholds.</param> /// <param name="filterType">A value indicating whether a neighboring smooth mode selects the alternate thresholds.</param>
/// <returns>The filter strength from zero for no filtering through three for the strongest kernel.</returns> /// <returns>The filter strength from zero for no filtering through three for the strongest kernel.</returns>
/// <remarks>Corresponds to <c>svt_aom_intra_edge_filter_strength</c> in SVT-AV1.</remarks>
private static int IntraEdgeFilterStrength(int width, int height, int delta, bool filterType) private static int IntraEdgeFilterStrength(int width, int height, int delta, bool filterType)
{ {
int d = Math.Abs(delta); int d = Math.Abs(delta);

2
src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundInterPredictor.cs

@ -232,7 +232,7 @@ internal static partial class Av1CompoundInterPredictor
ReadOnlySpan<short> horizontalCoefficients = GetCompoundCoefficients(horizontalFilter, horizontalPhase, width <= 4); ReadOnlySpan<short> horizontalCoefficients = GetCompoundCoefficients(horizontalFilter, horizontalPhase, width <= 4);
ReadOnlySpan<short> verticalCoefficients = GetCompoundCoefficients(verticalFilter, verticalPhase, height <= 4); ReadOnlySpan<short> verticalCoefficients = GetCompoundCoefficients(verticalFilter, verticalPhase, height <= 4);
// Libaom increases the first-round shift only for 12-bit input. This keeps the signed horizontal // The reference decoder increases the first-round shift only for 12-bit input. This keeps the signed horizontal
// intermediate within sixteen bits while preserving the same total Q14 convolution precision. // intermediate within sixteen bits while preserving the same total Q14 convolution precision.
int intermediateRange = bitDepth + FilterBits - Round0Bits + 2; int intermediateRange = bitDepth + FilterBits - Round0Bits + 2;
int round0 = Round0Bits + Math.Max(intermediateRange - 16, 0); int round0 = Round0Bits + Math.Max(intermediateRange - 16, 0);

2
src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterIntraMaskBuilder.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter;
internal static partial class Av1InterIntraMaskBuilder internal static partial class Av1InterIntraMaskBuilder
{ {
/// <summary> /// <summary>
/// Gets libaom's one-dimensional inter-intra alpha curve. /// Gets the reference decoder's one-dimensional inter-intra alpha curve.
/// </summary> /// </summary>
private static ReadOnlySpan<byte> InterIntraWeights => private static ReadOnlySpan<byte> InterIntraWeights =>
[ [

4
src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.Operator.cs

@ -474,7 +474,7 @@ internal static partial class Av1InterPredictor
return; return;
} }
// Twelve-bit samples require two additional first-pass rounding bits to keep libaom's signed intermediate // Twelve-bit samples require two additional first-pass rounding bits to keep the reference decoder's signed intermediate
// within sixteen bits. The second pass gives those bits back, preserving a total Q14 shift. // within sixteen bits. The second pass gives those bits back, preserving a total Q14 shift.
int intermediateRange = bitDepth + FilterBits - Round0Bits + 2; int intermediateRange = bitDepth + FilterBits - Round0Bits + 2;
int round0 = Round0Bits + Math.Max(intermediateRange - 16, 0); int round0 = Round0Bits + Math.Max(intermediateRange - 16, 0);
@ -757,7 +757,7 @@ internal static partial class Av1InterPredictor
/// <param name="tapCount">Receives the effective two-, four-, six-, or eight-tap length.</param> /// <param name="tapCount">Receives the effective two-, four-, six-, or eight-tap length.</param>
public static void GetEffectiveKernel(ReadOnlySpan<short> coefficients, out int firstCoefficient, out int tapCount) public static void GetEffectiveKernel(ReadOnlySpan<short> coefficients, out int firstCoefficient, out int tapCount)
{ {
// This matches libaom's get_filter_tap decision. Reducing symmetric zero endpoints avoids source loads and // This matches the reference decoder's get_filter_tap decision. Reducing symmetric zero endpoints avoids source loads and
// multiply-adds while retaining the original tap-to-source alignment through firstCoefficient. // multiply-adds while retaining the original tap-to-source alignment through firstCoefficient.
if (coefficients[0] != 0 || coefficients[7] != 0) if (coefficients[0] != 0 || coefficients[7] != 0)
{ {

2
src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.TwoDimensions.Byte.cs

@ -473,7 +473,7 @@ internal static partial class Av1InterPredictor
int roundOffset = (1 << (offsetBits - round1)) + (1 << (offsetBits - round1 - 1)); int roundOffset = (1 << (offsetBits - round1)) + (1 << (offsetBits - round1 - 1));
// The biased first pass keeps every intermediate nonnegative and representable by a signed 16-bit lane. // The biased first pass keeps every intermediate nonnegative and representable by a signed 16-bit lane.
// Removing both bias terms after the vertical Q7 filter reproduces libaom's single-reference rounding exactly. // Removing both bias terms after the vertical Q7 filter reproduces the reference decoder's single-reference rounding exactly.
for (int row = 0; row < height; row++) for (int row = 0; row < height; row++)
{ {
ref short scratchRow = ref Unsafe.Add(ref scratchBase, row * scratchStride); ref short scratchRow = ref Unsafe.Add(ref scratchBase, row * scratchStride);

2
src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.cs

@ -28,7 +28,7 @@ internal static partial class Av1InterPredictor
internal const int FilterBits = 7; internal const int FilterBits = 7;
/// <summary> /// <summary>
/// The normal first-round shift used by libaom single-reference convolution. /// The normal first-round shift used by the reference decoder single-reference convolution.
/// </summary> /// </summary>
internal const int Round0Bits = 3; internal const int Round0Bits = 3;

2
src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1ReferenceScale.cs

@ -79,7 +79,7 @@ internal readonly struct Av1ReferenceScale
public int ScaleVertical(int value) => Scale(value, this.VerticalScale); public int ScaleVertical(int value) => Scale(value, this.VerticalScale);
/// <summary> /// <summary>
/// Applies libaom's signed fixed-point rounding without relying on implementation-defined negative shifts. /// Applies the reference decoder's signed fixed-point rounding without relying on implementation-defined negative shifts.
/// </summary> /// </summary>
private static int Scale(int value, int scale) private static int Scale(int value, int scale)
{ {

8
src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1WedgeMask.cs

@ -14,7 +14,7 @@ internal static class Av1WedgeMask
private const int MasterSize = 64; private const int MasterSize = 64;
/// <summary> /// <summary>
/// Gets the odd-row oblique prototype defined by libaom. /// Gets the odd-row oblique prototype defined by the reference decoder.
/// </summary> /// </summary>
private static ReadOnlySpan<byte> MasterObliqueOdd => private static ReadOnlySpan<byte> MasterObliqueOdd =>
[ [
@ -25,7 +25,7 @@ internal static class Av1WedgeMask
]; ];
/// <summary> /// <summary>
/// Gets the even-row oblique prototype defined by libaom. /// Gets the even-row oblique prototype defined by the reference decoder.
/// </summary> /// </summary>
private static ReadOnlySpan<byte> MasterObliqueEven => private static ReadOnlySpan<byte> MasterObliqueEven =>
[ [
@ -36,7 +36,7 @@ internal static class Av1WedgeMask
]; ];
/// <summary> /// <summary>
/// Gets the vertical prototype defined by libaom. /// Gets the vertical prototype defined by the reference decoder.
/// </summary> /// </summary>
private static ReadOnlySpan<byte> MasterVertical => private static ReadOnlySpan<byte> MasterVertical =>
[ [
@ -183,7 +183,7 @@ internal static class Av1WedgeMask
} }
/// <summary> /// <summary>
/// Gets libaom's canonical sign flip for a block and wedge index. /// Gets the reference decoder's canonical sign flip for a block and wedge index.
/// </summary> /// </summary>
private static bool GetSignFlip(Av1BlockSize blockSize, int wedgeIndex) private static bool GetSignFlip(Av1BlockSize blockSize, int wedgeIndex)
{ {

2
src/ImageSharp/Formats/Heif/Av1/Prediction/IntraBlockCopy/Av1IntraBlockCopyBilinearPredictor.Operator.cs

@ -124,7 +124,7 @@ internal static partial class Av1IntraBlockCopyBilinearPredictor
/// Applies the separable two-dimensional interpolation required when both source axes have a half-sample phase. /// Applies the separable two-dimensional interpolation required when both source axes have a half-sample phase.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The offsets in libaom's separable two-pass implementation cancel algebraically to /// The offsets in the reference decoder's separable two-pass implementation cancel algebraically to
/// <c>(topLeft + topRight + bottomLeft + bottomRight + 2) &gt;&gt; 2</c>, so the closed operator produces the exact /// <c>(topLeft + topRight + bottomLeft + bottomRight + 2) &gt;&gt; 2</c>, so the closed operator produces the exact
/// result directly without an intermediate image buffer. /// result directly without an intermediate image buffer.
/// ///

8
src/ImageSharp/Formats/Heif/Av1/ReferenceFrames/Av1ReferenceFrameDerivation.cs

@ -74,7 +74,7 @@ internal static class Av1ReferenceFrameDerivation
if (!slotOccupancy[lastMapIndex]) if (!slotOccupancy[lastMapIndex])
{ {
// Unlike an unused empty slot, the explicitly signaled LAST slot must own a decoded frame before any // Unlike an unused empty slot, the explicitly signaled LAST slot must own a decoded frame before any
// derived mapping can be consumed. libaom rejects the missing reference at this frame-header boundary. // derived mapping can be consumed. the reference decoder rejects the missing reference at this frame-header boundary.
throw new InvalidImageContentException("An AV1 inter frame requests an unavailable LAST reference."); throw new InvalidImageContentException("An AV1 inter frame requests an unavailable LAST reference.");
} }
@ -98,7 +98,7 @@ internal static class Av1ReferenceFrameDerivation
if (!slotOccupancy[mapIndex]) if (!slotOccupancy[mapIndex])
{ {
// libaom gives absent reference buffers sort index -1. Keeping empty managed slots in the same // the reference decoder gives absent reference buffers sort index -1. Keeping empty managed slots in the same
// leading partition prevents their stale order hints from participating in temporal selection. // leading partition prevents their stale order hints from participating in temporal selection.
continue; continue;
} }
@ -132,7 +132,7 @@ internal static class Av1ReferenceFrameDerivation
throw new InvalidImageContentException("An AV1 inter frame requests a current or future frame as GOLDEN."); throw new InvalidImageContentException("An AV1 inter frame requests a current or future frame as GOLDEN.");
} }
// libaom sorts first by shifted output order and then by reference-map index. The explicit tie break is // the reference decoder sorts first by shifted output order and then by reference-map index. The explicit tie break is
// normative: equal order hints select the highest map index for latest references and the lowest for earliest // normative: equal order hints select the highest map index for latest references and the lowest for earliest
// references. Insertion sort is bounded to eight inline entries and does not allocate or require general sort // references. Insertion sort is bounded to eight inline entries and does not allocate or require general sort
// infrastructure at the frame-header boundary. // infrastructure at the frame-header boundary.
@ -194,7 +194,7 @@ internal static class Av1ReferenceFrameDerivation
if (backwardStartIndex <= backwardEndIndex) if (backwardStartIndex <= backwardEndIndex)
{ {
// ALTREF receives the frame farthest into the future. The sorted-map-index tie break selects the highest // ALTREF receives the frame farthest into the future. The sorted-map-index tie break selects the highest
// slot when multiple frames share that order hint, matching both the specification and libaom. // slot when multiple frames share that order hint, matching both the specification and the reference decoder.
referenceFrameIndices[alternateReferenceIndex] = (uint)referenceInfo[backwardEndIndex].MapIndex; referenceFrameIndices[alternateReferenceIndex] = (uint)referenceInfo[backwardEndIndex].MapIndex;
assignedReferences[alternateReferenceIndex] = true; assignedReferences[alternateReferenceIndex] = true;
backwardEndIndex--; backwardEndIndex--;

2
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs

@ -147,7 +147,7 @@ internal struct Av1BlockModeInfo
/// Gets the interpolation filters used for vertical and horizontal subpixel prediction. /// Gets the interpolation filters used for vertical and horizontal subpixel prediction.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Index zero is the vertical filter and index one is the horizontal filter, matching libaom's /// Index zero is the vertical filter and index one is the horizontal filter, matching the reference decoder's
/// <c>InterpFilters.y_filter</c> and <c>InterpFilters.x_filter</c> layout. /// <c>InterpFilters.y_filter</c> and <c>InterpFilters.x_filter</c> layout.
/// </remarks> /// </remarks>
[UnscopedRef] [UnscopedRef]

20
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.MotionField.cs

@ -129,7 +129,7 @@ internal partial class Av1FrameInfo
if (frameHeader.IsIntra) if (frameHeader.IsIntra)
{ {
// Intra frames retain an empty source field. They can occupy reference slots, but libaom rejects them as // Intra frames retain an empty source field. They can occupy reference slots, but the reference decoder rejects them as
// projection sources before consulting their reference-order-hint metadata. // projection sources before consulting their reference-order-hint metadata.
return; return;
} }
@ -138,7 +138,7 @@ internal partial class Av1FrameInfo
ReadOnlySpan<uint> referenceFrameIndices = frameHeader.GetReferenceFrameIndices(); ReadOnlySpan<uint> referenceFrameIndices = frameHeader.GetReferenceFrameIndices();
ObuOrderHintInfo orderHintInfo = sequenceHeader.OrderHintInfo; ObuOrderHintInfo orderHintInfo = sequenceHeader.OrderHintInfo;
// Capture the seven logical-role order hints before this frame refreshes any physical map slots. Libaom keeps // Capture the seven logical-role order hints before this frame refreshes any physical map slots. The reference decoder keeps
// the same snapshot on RefCntBuffer so a later frame can project this frame's stored motion vectors. // the same snapshot on RefCntBuffer so a later frame can project this frame's stored motion vectors.
for (int referenceIndex = 0; referenceIndex < Av1Constants.ReferencesPerFrame; referenceIndex++) for (int referenceIndex = 0; referenceIndex < Av1Constants.ReferencesPerFrame; referenceIndex++)
{ {
@ -174,7 +174,7 @@ internal partial class Av1FrameInfo
return; return;
} }
// libaom aligns the projected field stride to the largest superblock even for a 64x64 sequence. This keeps // the reference decoder aligns the projected field stride to the largest superblock even for a 64x64 sequence. This keeps
// later temporal-candidate addressing independent of the current sequence's selected superblock size. // later temporal-candidate addressing independent of the current sequence's selected superblock size.
int alignedModeInfoColumnCount = Av1Math.AlignPowerOf2( int alignedModeInfoColumnCount = Av1Math.AlignPowerOf2(
this.activeModeInfoColumnCount, this.activeModeInfoColumnCount,
@ -195,7 +195,7 @@ internal partial class Av1FrameInfo
uint alternateOfLastOrderHint = lastFrame.FrameInfo.motionFieldReferenceOrderHints[(int)Av1ReferenceFrameType.Alternate]; uint alternateOfLastOrderHint = lastFrame.FrameInfo.motionFieldReferenceOrderHints[(int)Av1ReferenceFrameType.Alternate];
// A LAST frame whose ALTREF order matches GOLDEN is an overlay. Projecting it would duplicate the overlay's // A LAST frame whose ALTREF order matches GOLDEN is an overlay. Projecting it would duplicate the overlay's
// temporal source, but libaom still consumes one position from the three-source projection budget. // temporal source, but the reference decoder still consumes one position from the three-source projection budget.
if (alternateOfLastOrderHint != goldenFrame.FrameHeader.OrderHint) if (alternateOfLastOrderHint != goldenFrame.FrameHeader.OrderHint)
{ {
_ = this.ProjectMotionField(sequenceHeader, frameHeader, lastFrame, reverseDirection: true); _ = this.ProjectMotionField(sequenceHeader, frameHeader, lastFrame, reverseDirection: true);
@ -327,7 +327,7 @@ internal partial class Av1FrameInfo
Span<Av1ReferenceFrameType> referenceFrames = modeInfo.ReferenceFrames; Span<Av1ReferenceFrameType> referenceFrames = modeInfo.ReferenceFrames;
Span<Av1MotionVector> motionVectors = modeInfo.MotionVectors; Span<Av1MotionVector> motionVectors = modeInfo.MotionVectors;
// Compound blocks may offer two vectors. libaom retains the last eligible forward-or-past reference after // Compound blocks may offer two vectors. the reference decoder retains the last eligible forward-or-past reference after
// excluding same-order, future, and out-of-range vectors, so preserve that overwrite order exactly. // excluding same-order, future, and out-of-range vectors, so preserve that overwrite order exactly.
for (int referenceIndex = 0; referenceIndex < 2; referenceIndex++) for (int referenceIndex = 0; referenceIndex < 2; referenceIndex++)
{ {
@ -493,16 +493,12 @@ internal partial class Av1FrameInfo
this.ownerCount--; this.ownerCount--;
if (this.ownerCount == 0) if (this.ownerCount == 0)
{ {
// Frame-sized motion and palette storage remains addressable through retained mode information. Return // Frame-sized motion storage remains addressable through retained references. Return it only after tile,
// all of it together only after tile, reference, presentation, and decoder-result owners are gone. // reference, presentation, and decoder-result owners are gone.
this.retainedMotionField?.Dispose(); this.retainedMotionField?.Dispose();
this.retainedMotionField = null; this.retainedMotionField = null;
this.temporalMotionField?.Dispose(); this.temporalMotionField?.Dispose();
this.temporalMotionField = null; this.temporalMotionField = null;
this.lumaPaletteColorIndexMap?.Dispose();
this.lumaPaletteColorIndexMap = null;
this.chromaPaletteColorIndexMap?.Dispose();
this.chromaPaletteColorIndexMap = null;
} }
} }
@ -532,7 +528,7 @@ internal partial class Av1FrameInfo
int baseBlockColumn = (blockColumn >> 3) << 3; int baseBlockColumn = (blockColumn >> 3) << 3;
// One field cell spans 8 samples, while vectors use one-eighth-sample units; dividing by 64 converts between // One field cell spans 8 samples, while vectors use one-eighth-sample units; dividing by 64 converts between
// them. C# integer division truncates toward zero, matching libaom's explicit signed-shift construction. // them. C# integer division truncates toward zero, matching the reference decoder's explicit signed-shift construction.
int rowOffset = motionVector.Row / (1 << MotionVectorToFieldOffsetShift); int rowOffset = motionVector.Row / (1 << MotionVectorToFieldOffsetShift);
int columnOffset = motionVector.Column / (1 << MotionVectorToFieldOffsetShift); int columnOffset = motionVector.Column / (1 << MotionVectorToFieldOffsetShift);
projectedRow = reverseDirection ? blockRow - rowOffset : blockRow + rowOffset; projectedRow = reverseDirection ? blockRow - rowOffset : blockRow + rowOffset;

81
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs

@ -56,36 +56,6 @@ internal partial class Av1FrameInfo : IDisposable
/// </summary> /// </summary>
private readonly int subsamplingFactor; private readonly int subsamplingFactor;
/// <summary>
/// The aligned luma palette-map width in samples.
/// </summary>
private readonly int lumaPaletteColorIndexMapWidth;
/// <summary>
/// The aligned luma palette-map height in samples.
/// </summary>
private readonly int lumaPaletteColorIndexMapHeight;
/// <summary>
/// The aligned chroma palette-map width in samples.
/// </summary>
private readonly int chromaPaletteColorIndexMapWidth;
/// <summary>
/// The aligned chroma palette-map height in samples.
/// </summary>
private readonly int chromaPaletteColorIndexMapHeight;
/// <summary>
/// Owns row-addressable luma palette indices for the frame.
/// </summary>
private Buffer2D<byte>? lumaPaletteColorIndexMap;
/// <summary>
/// Owns row-addressable chroma palette indices for the frame.
/// </summary>
private Buffer2D<byte>? chromaPaletteColorIndexMap;
/// <summary> /// <summary>
/// Stores one addressing view for each frame superblock. /// Stores one addressing view for each frame superblock.
/// </summary> /// </summary>
@ -210,10 +180,6 @@ internal partial class Av1FrameInfo : IDisposable
// Chroma capacity scales by two for each sampled axis: 4:4:4 => 0, 4:2:2 => 1, 4:2:0 => 2. // Chroma capacity scales by two for each sampled axis: 4:4:4 => 0, 4:2:2 => 1, 4:2:0 => 2.
this.subsamplingFactor = (subX && subY) ? 2 : (subX && !subY) ? 1 : (!subX && !subY) ? 0 : -1; this.subsamplingFactor = (subX && subY) ? 2 : (subX && !subY) ? 1 : (!subX && !subY) ? 0 : -1;
Guard.IsFalse(this.subsamplingFactor == -1, nameof(this.subsamplingFactor), "Invalid combination of subsampling."); Guard.IsFalse(this.subsamplingFactor == -1, nameof(this.subsamplingFactor), "Invalid combination of subsampling.");
this.lumaPaletteColorIndexMapWidth = superblockAlignedWidth;
this.lumaPaletteColorIndexMapHeight = superblockAlignedHeight;
this.chromaPaletteColorIndexMapWidth = superblockAlignedWidth >> (subX ? 1 : 0);
this.chromaPaletteColorIndexMapHeight = superblockAlignedHeight >> (subY ? 1 : 0);
int lumaCoefficientCountPerSuperblock = this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo; int lumaCoefficientCountPerSuperblock = this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo;
int chromaCoefficientCountPerSuperblock = lumaCoefficientCountPerSuperblock >> this.subsamplingFactor; int chromaCoefficientCountPerSuperblock = lumaCoefficientCountPerSuperblock >> this.subsamplingFactor;
this.coefficientsY = new int[superblockCount * lumaCoefficientCountPerSuperblock]; this.coefficientsY = new int[superblockCount * lumaCoefficientCountPerSuperblock];
@ -238,51 +204,6 @@ internal partial class Av1FrameInfo : IDisposable
/// </summary> /// </summary>
public int SuperblockModeInfoSize => this.modeInfoSizePerSuperblock; public int SuperblockModeInfoSize => this.modeInfoSizePerSuperblock;
/// <summary>
/// Gets frame-owned row-addressable palette-map storage for one coding block.
/// </summary>
/// <param name="configuration">The decoder configuration providing frame storage.</param>
/// <param name="planeType">The luma or shared chroma plane class.</param>
/// <param name="bounds">The block bounds in plane samples.</param>
/// <returns>The palette-map region assigned to the coding block.</returns>
public Buffer2DRegion<byte> GetPaletteColorIndexMap(
Configuration configuration,
Av1PlaneType planeType,
Rectangle bounds)
{
Buffer2D<byte>? buffer;
if (planeType == Av1PlaneType.Y)
{
buffer = this.lumaPaletteColorIndexMap;
if (buffer is null)
{
// A 2D allocation may contain multiple memory groups, but row alignment guarantees that every
// palette row remains contiguous for entropy decoding and SIMD reconstruction.
buffer = configuration.MemoryAllocator.Allocate2D<byte>(
this.lumaPaletteColorIndexMapWidth,
this.lumaPaletteColorIndexMapHeight);
this.lumaPaletteColorIndexMap = buffer;
}
}
else
{
buffer = this.chromaPaletteColorIndexMap;
if (buffer is null)
{
buffer = configuration.MemoryAllocator.Allocate2D<byte>(
this.chromaPaletteColorIndexMapWidth,
this.chromaPaletteColorIndexMapHeight);
this.chromaPaletteColorIndexMap = buffer;
}
}
// Partition traversal assigns non-overlapping frame regions, so retaining a view records the complete
// identify-time syntax without copying block maps or allocating storage for each coding block.
return new Buffer2DRegion<byte>(buffer, bounds);
}
/// <summary> /// <summary>
/// Initializes the active frame's contiguous segment map and applies whole-map inheritance when requested. /// Initializes the active frame's contiguous segment map and applies whole-map inheritance when requested.
/// </summary> /// </summary>
@ -341,7 +262,7 @@ internal partial class Av1FrameInfo : IDisposable
primaryReferenceFrameInfo.segmentIdColumnCount != this.segmentIdColumnCount || primaryReferenceFrameInfo.segmentIdColumnCount != this.segmentIdColumnCount ||
primaryReferenceFrameInfo.segmentIdRowCount != this.segmentIdRowCount) primaryReferenceFrameInfo.segmentIdRowCount != this.segmentIdRowCount)
{ {
// libaom exposes the prior map only when both mode-info dimensions match the active frame. Treating a // the reference decoder exposes the prior map only when both mode-info dimensions match the active frame. Treating a
// differently sized retained map as absent prevents coordinates from being reinterpreted with a new stride. // differently sized retained map as absent prevents coordinates from being reinterpreted with a new stride.
return 0; return 0;
} }

194
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs

@ -101,6 +101,21 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
/// </summary> /// </summary>
private readonly Av1LevelBuffer coefficientLevels; private readonly Av1LevelBuffer coefficientLevels;
/// <summary>
/// Reusable luma palette indices for the coding blocks in one superblock.
/// </summary>
private readonly Buffer2D<byte> lumaPaletteColorIndexMap;
/// <summary>
/// Reusable chroma palette indices for the coding blocks in one superblock.
/// </summary>
private readonly Buffer2D<byte> chromaPaletteColorIndexMap;
/// <summary>
/// Indicates whether this reader owns and disposes the palette maps.
/// </summary>
private readonly bool ownsPaletteColorIndexMaps;
/// <summary> /// <summary>
/// Reusable storage for the eight spatial displacement-vector candidates permitted by AV1. /// Reusable storage for the eight spatial displacement-vector candidates permitted by AV1.
/// </summary> /// </summary>
@ -131,11 +146,6 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
/// </summary> /// </summary>
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary>
/// Reconstructs each parsed superblock when pixel decoding is requested; otherwise, tile parsing is metadata-only.
/// </summary>
private readonly IAv1FrameDecoder? frameDecoder;
/// <summary> /// <summary>
/// The decoder-session entropy contexts reused by every tile in the current frame. /// The decoder-session entropy contexts reused by every tile in the current frame.
/// </summary> /// </summary>
@ -182,12 +192,74 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
Av1FrameEntropyContexts entropyContexts, Av1FrameEntropyContexts entropyContexts,
Av1FrameEntropyContext? primaryReferenceContext, Av1FrameEntropyContext? primaryReferenceContext,
Av1ReferenceFrameStore? referenceFrames) Av1ReferenceFrameStore? referenceFrames)
: this(configuration, sequenceHeader, frameHeader, entropyContexts, primaryReferenceContext, referenceFrames, null, null, true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1TileReader"/> class with decoder-session entropy and palette state.
/// </summary>
/// <param name="configuration">The decoder configuration.</param>
/// <param name="sequenceHeader">The active AV1 sequence header.</param>
/// <param name="frameHeader">The frame header whose tiles will be parsed.</param>
/// <param name="entropyContexts">The entropy contexts reused by the owning decoder session.</param>
/// <param name="primaryReferenceContext">
/// The retained primary-reference entropy context, or <see langword="null"/> when the frame selects defaults.
/// </param>
/// <param name="referenceFrames">The retained reconstructed frames.</param>
/// <param name="lumaPaletteColorIndexMap">The decoder-session luma palette scratch map.</param>
/// <param name="chromaPaletteColorIndexMap">The decoder-session chroma palette scratch map.</param>
public Av1TileReader(
Configuration configuration,
ObuSequenceHeader sequenceHeader,
ObuFrameHeader frameHeader,
Av1FrameEntropyContexts entropyContexts,
Av1FrameEntropyContext? primaryReferenceContext,
Av1ReferenceFrameStore referenceFrames,
Buffer2D<byte> lumaPaletteColorIndexMap,
Buffer2D<byte> chromaPaletteColorIndexMap)
: this(
configuration,
sequenceHeader,
frameHeader,
entropyContexts,
primaryReferenceContext,
referenceFrames,
lumaPaletteColorIndexMap,
chromaPaletteColorIndexMap,
false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1TileReader"/> class with explicit palette-map ownership.
/// </summary>
/// <param name="configuration">The decoder configuration.</param>
/// <param name="sequenceHeader">The active AV1 sequence header.</param>
/// <param name="frameHeader">The frame header whose tiles will be parsed.</param>
/// <param name="entropyContexts">The entropy contexts reused by the owning decoder session.</param>
/// <param name="primaryReferenceContext">The retained primary-reference entropy context.</param>
/// <param name="referenceFrames">The retained reconstructed frames.</param>
/// <param name="lumaPaletteColorIndexMap">A shared luma palette map, or <see langword="null"/> when the reader owns one.</param>
/// <param name="chromaPaletteColorIndexMap">A shared chroma palette map, or <see langword="null"/> when the reader owns one.</param>
/// <param name="ownsPaletteColorIndexMaps">Whether the reader owns and disposes the palette maps.</param>
private Av1TileReader(
Configuration configuration,
ObuSequenceHeader sequenceHeader,
ObuFrameHeader frameHeader,
Av1FrameEntropyContexts entropyContexts,
Av1FrameEntropyContext? primaryReferenceContext,
Av1ReferenceFrameStore? referenceFrames,
Buffer2D<byte>? lumaPaletteColorIndexMap,
Buffer2D<byte>? chromaPaletteColorIndexMap,
bool ownsPaletteColorIndexMaps)
{ {
this.FrameHeader = frameHeader; this.FrameHeader = frameHeader;
this.configuration = configuration; this.configuration = configuration;
this.SequenceHeader = sequenceHeader; this.SequenceHeader = sequenceHeader;
this.entropyContexts = entropyContexts; this.entropyContexts = entropyContexts;
this.referenceFrames = referenceFrames; this.referenceFrames = referenceFrames;
this.ownsPaletteColorIndexMaps = ownsPaletteColorIndexMaps;
this.entropyContexts.BeginFrame(frameHeader.QuantizationParameters.BaseQIndex, primaryReferenceContext); this.entropyContexts.BeginFrame(frameHeader.QuantizationParameters.BaseQIndex, primaryReferenceContext);
// FrameInfo owns all traversal-order records and coefficient storage produced by the tile readers. // FrameInfo owns all traversal-order records and coefficient storage produced by the tile readers.
@ -245,6 +317,38 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
throw; throw;
} }
if (ownsPaletteColorIndexMaps)
{
Buffer2D<byte>? ownedLumaPaletteColorIndexMap = null;
Buffer2D<byte>? ownedChromaPaletteColorIndexMap = null;
try
{
// Standalone syntax readers have no decoder-session owner. They still use the same fixed
// maximum-superblock bound and return both maps when the reader is disposed.
int paletteMapLength = 1 << Av1Constants.MaxSuperBlockSizeLog2;
ownedLumaPaletteColorIndexMap = configuration.MemoryAllocator.Allocate2D<byte>(paletteMapLength, paletteMapLength);
ownedChromaPaletteColorIndexMap = configuration.MemoryAllocator.Allocate2D<byte>(paletteMapLength, paletteMapLength);
this.lumaPaletteColorIndexMap = ownedLumaPaletteColorIndexMap;
this.chromaPaletteColorIndexMap = ownedChromaPaletteColorIndexMap;
}
catch
{
ownedChromaPaletteColorIndexMap?.Dispose();
ownedLumaPaletteColorIndexMap?.Dispose();
this.coefficientLevels.Dispose();
this.leftNeighborContext.Dispose();
this.aboveNeighborContext.Dispose();
this.FrameInfo.Dispose();
throw;
}
}
else
{
this.lumaPaletteColorIndexMap = lumaPaletteColorIndexMap!;
this.chromaPaletteColorIndexMap = chromaPaletteColorIndexMap!;
}
if (referenceFrames is not null) if (referenceFrames is not null)
{ {
try try
@ -255,9 +359,15 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
} }
catch catch
{ {
this.aboveNeighborContext.Dispose(); if (this.ownsPaletteColorIndexMaps)
this.leftNeighborContext.Dispose(); {
this.chromaPaletteColorIndexMap.Dispose();
this.lumaPaletteColorIndexMap.Dispose();
}
this.coefficientLevels.Dispose(); this.coefficientLevels.Dispose();
this.leftNeighborContext.Dispose();
this.aboveNeighborContext.Dispose();
this.FrameInfo.Dispose(); this.FrameInfo.Dispose();
throw; throw;
} }
@ -273,7 +383,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
/// <param name="frameDecoder">The frame decoder that reconstructs each parsed superblock.</param> /// <param name="frameDecoder">The frame decoder that reconstructs each parsed superblock.</param>
public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1FrameDecoder frameDecoder) public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1FrameDecoder frameDecoder)
: this(configuration, sequenceHeader, frameHeader) : this(configuration, sequenceHeader, frameHeader)
=> this.frameDecoder = frameDecoder; => this.FrameDecoder = frameDecoder;
/// <summary> /// <summary>
/// Gets the default self-guided restoration projection coefficients for each color plane. /// Gets the default self-guided restoration projection coefficients for each color plane.
@ -332,6 +442,11 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
/// </summary> /// </summary>
public Av1FrameInfo FrameInfo { get; } public Av1FrameInfo FrameInfo { get; }
/// <summary>
/// Gets or sets the optional decoder that reconstructs each superblock immediately after its syntax is parsed.
/// </summary>
public IAv1FrameDecoder? FrameDecoder { get; set; }
/// <summary> /// <summary>
/// Gets the completed frame entropy context selected by the context-update tile. /// Gets the completed frame entropy context selected by the context-update tile.
/// </summary> /// </summary>
@ -350,6 +465,12 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
this.aboveNeighborContext.Dispose(); this.aboveNeighborContext.Dispose();
this.leftNeighborContext.Dispose(); this.leftNeighborContext.Dispose();
this.coefficientLevels.Dispose(); this.coefficientLevels.Dispose();
if (this.ownsPaletteColorIndexMaps)
{
this.lumaPaletteColorIndexMap.Dispose();
this.chromaPaletteColorIndexMap.Dispose();
}
this.FrameInfo.Dispose(); this.FrameInfo.Dispose();
} }
@ -358,7 +479,6 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
/// </summary> /// </summary>
/// <param name="tileData">The entropy-coded tile payload.</param> /// <param name="tileData">The entropy-coded tile payload.</param>
/// <param name="tileNum">The zero-based tile index in row-major order.</param> /// <param name="tileNum">The zero-based tile index in row-major order.</param>
/// <remarks>Corresponds to <c>decode_tile</c> in libaom.</remarks>
public void ReadTile(Span<byte> tileData, int tileNum) public void ReadTile(Span<byte> tileData, int tileNum)
{ {
// AV1 tiles never inherit adaptation from another tile in the same frame. Reusing one graph is safe because // AV1 tiles never inherit adaptation from another tile in the same frame. Reusing one graph is safe because
@ -366,7 +486,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
// constructed. // constructed.
this.entropyContexts.Working.CopyFrom(this.entropyContexts.Base); this.entropyContexts.Working.CopyFrom(this.entropyContexts.Base);
// The frame syntax exposes a disable flag, while the range reader follows libaom's positive // The frame syntax exposes a disable flag, while the range reader follows the reference decoder's positive
// allow_update_cdf convention. // allow_update_cdf convention.
Av1SymbolDecoder reader = new( Av1SymbolDecoder reader = new(
this.configuration, this.configuration,
@ -424,7 +544,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
this.ParsePartition(ref reader, modeInfoPosition, superBlockSize, superblockInfo, tileInfo); this.ParsePartition(ref reader, modeInfoPosition, superBlockSize, superblockInfo, tileInfo);
// Identify-only parsing omits a frame decoder but still populates the complete syntax model. // Identify-only parsing omits a frame decoder but still populates the complete syntax model.
this.frameDecoder?.DecodeSuperblock(modeInfoPosition, superblockInfo, tileInfo); this.FrameDecoder?.DecodeSuperblock(modeInfoPosition, superblockInfo, tileInfo);
} }
} }
@ -434,7 +554,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
if (!this.FrameHeader.DisableFrameEndUpdateCdf && tileNum == this.FrameHeader.TilesInfo.ContextUpdateTileId) if (!this.FrameHeader.DisableFrameEndUpdateCdf && tileNum == this.FrameHeader.TilesInfo.ContextUpdateTileId)
{ {
// libaom publishes only context_update_tile_id after every tile has independently started from the frame // the reference decoder publishes only context_update_tile_id after every tile has independently started from the frame
// base, then clears its CDF counters. Snapshotting into a third reusable graph preserves the unchanged base // base, then clears its CDF counters. Snapshotting into a third reusable graph preserves the unchanged base
// for tiles that follow the selected tile in bitstream order. // for tiles that follow the selected tile in bitstream order.
this.entropyContexts.Working.SnapshotTo(this.entropyContexts.Published); this.entropyContexts.Working.SnapshotTo(this.entropyContexts.Published);
@ -692,7 +812,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
if (subSize.GetSubsampled(colorConfig.SubSamplingX, colorConfig.SubSamplingY) == Av1BlockSize.Invalid) if (subSize.GetSubsampled(colorConfig.SubSamplingX, colorConfig.SubSamplingY) == Av1BlockSize.Invalid)
{ {
// Luma partition syntax can describe a sub-8x8 shape that has no legal representation after chroma // Luma partition syntax can describe a sub-8x8 shape that has no legal representation after chroma
// subsampling. Reject it before any block state is published, matching libaom's decode_partition boundary. // subsampling. Reject it before any block state is published, matching the reference decoder's decode_partition boundary.
throw new InvalidImageContentException($"The decoded AV1 block size {subSize} is invalid for the sequence chroma subsampling."); throw new InvalidImageContentException($"The decoded AV1 block size {subSize} is invalid for the sequence chroma subsampling.");
} }
@ -1113,7 +1233,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
int leftContextOffset = startY - superblockRow; int leftContextOffset = startY - superblockRow;
// Above contexts are tile-column relative, while left contexts are reused from the start of each // Above contexts are tile-column relative, while left contexts are reused from the start of each
// superblock row. Slicing both arrays here gives the entropy derivation the same pointer bases as libaom. // superblock row. Slicing both arrays here gives the entropy derivation the same pointer bases as the reference decoder.
Av1TransformBlockContext transformBlockContext = this.GetTransformBlockContext( Av1TransformBlockContext transformBlockContext = this.GetTransformBlockContext(
transformSize, transformSize,
plane, plane,
@ -1313,7 +1433,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
int transformBlockUnitWideCount = transformSize.Get4x4WideCount(); int transformBlockUnitWideCount = transformSize.Get4x4WideCount();
int transformBlockUnitHighCount = transformSize.Get4x4HighCount(); int transformBlockUnitHighCount = transformSize.Get4x4HighCount();
// libaom tests the context bytes through packed native loads. Enumerating the same transform-width and // the reference decoder tests the context bytes through packed native loads. Enumerating the same transform-width and
// transform-height entries avoids unaligned reads while preserving the required any-nonzero result. // transform-height entries avoids unaligned reads while preserving the required any-nonzero result.
for (int i = 0; i < transformBlockUnitWideCount; i++) for (int i = 0; i < transformBlockUnitWideCount; i++)
{ {
@ -1442,7 +1562,6 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
/// <param name="partitionInfo">The current coding block.</param> /// <param name="partitionInfo">The current coding block.</param>
/// <param name="superblockInfo">The containing superblock.</param> /// <param name="superblockInfo">The containing superblock.</param>
/// <param name="tileInfo">The active tile boundaries.</param> /// <param name="tileInfo">The active tile boundaries.</param>
/// <remarks>Implements AV1 section 5.11.16 and corresponds to <c>read_tx_size</c> in libaom.</remarks>
private void ReadBlockTransformSize( private void ReadBlockTransformSize(
ref Av1SymbolDecoder reader, ref Av1SymbolDecoder reader,
Point modeInfoLocation, Point modeInfoLocation,
@ -1722,7 +1841,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
int planeColumn = idx >> (subX ? 1 : 0); int planeColumn = idx >> (subX ? 1 : 0);
// The 64x64 region cursor is expressed on the luma grid. Chroma transform offsets use the // The 64x64 region cursor is expressed on the luma grid. Chroma transform offsets use the
// target plane's 4x4 grid, matching libaom's row/column subsampling before transform traversal. // target plane's 4x4 grid, matching the reference decoder's row/column subsampling before transform traversal.
for (int blockRow = planeRow; blockRow < unitHeight; blockRow += stepRow) for (int blockRow = planeRow; blockRow < unitHeight; blockRow += stepRow)
{ {
for (int blockColumn = planeColumn; blockColumn < unitWidth; blockColumn += stepColumn) for (int blockColumn = planeColumn; blockColumn < unitWidth; blockColumn += stepColumn)
@ -1786,14 +1905,9 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
out int rows, out int rows,
out int columns); out int columns);
Buffer2DRegion<byte> colorIndexMap = this.FrameInfo.GetPaletteColorIndexMap( Point position = modeInfo.PositionInSuperblock;
this.configuration, Rectangle bounds = new(position.X << Av1Constants.ModeInfoSizeLog2, position.Y << Av1Constants.ModeInfoSizeLog2, planeWidth, planeHeight);
Av1PlaneType.Y, Buffer2DRegion<byte> colorIndexMap = new(this.lumaPaletteColorIndexMap, bounds);
new Rectangle(
partitionInfo.ColumnIndex << Av1Constants.ModeInfoSizeLog2,
partitionInfo.RowIndex << Av1Constants.ModeInfoSizeLog2,
planeWidth,
planeHeight));
DecodePaletteColorMap( DecodePaletteColorMap(
ref reader, ref reader,
@ -1805,7 +1919,10 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
columns, columns,
colorIndexMap); colorIndexMap);
modeInfo.SetPaletteColorIndexMap(Av1PlaneType.Y, colorIndexMap); if (this.FrameDecoder is not null)
{
modeInfo.SetPaletteColorIndexMap(Av1PlaneType.Y, colorIndexMap);
}
} }
if (modeInfo.GetPaletteSize(Av1PlaneType.Uv) != 0) if (modeInfo.GetPaletteSize(Av1PlaneType.Uv) != 0)
@ -1821,14 +1938,9 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
int subX = this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; int subX = this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0;
int subY = this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; int subY = this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0;
Buffer2DRegion<byte> colorIndexMap = this.FrameInfo.GetPaletteColorIndexMap( Point position = modeInfo.PositionInSuperblock;
this.configuration, Rectangle bounds = new((position.X << Av1Constants.ModeInfoSizeLog2) >> subX, (position.Y << Av1Constants.ModeInfoSizeLog2) >> subY, planeWidth, planeHeight);
Av1PlaneType.Uv, Buffer2DRegion<byte> colorIndexMap = new(this.chromaPaletteColorIndexMap, bounds);
new Rectangle(
(partitionInfo.ColumnIndex << Av1Constants.ModeInfoSizeLog2) >> subX,
(partitionInfo.RowIndex << Av1Constants.ModeInfoSizeLog2) >> subY,
planeWidth,
planeHeight));
DecodePaletteColorMap( DecodePaletteColorMap(
ref reader, ref reader,
@ -1840,7 +1952,10 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
columns, columns,
colorIndexMap); colorIndexMap);
modeInfo.SetPaletteColorIndexMap(Av1PlaneType.Uv, colorIndexMap); if (this.FrameDecoder is not null)
{
modeInfo.SetPaletteColorIndexMap(Av1PlaneType.Uv, colorIndexMap);
}
} }
} }
@ -3011,7 +3126,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
if (segmentationParameters.SegmentationUpdateMap == 0) if (segmentationParameters.SegmentationUpdateMap == 0)
{ {
// The frame map was inherited as one contiguous copy during reader construction. Resolve the same clipped // The frame map was inherited as one contiguous copy during reader construction. Resolve the same clipped
// minimum that libaom obtains from last_frame_seg_map so block state and the already copied map agree. // minimum that the reference decoder obtains from last_frame_seg_map so block state and the already copied map agree.
modeInfo.SegmentId = this.FrameInfo.GetPredictedSegmentId(this.primaryReferenceFrameInfo, modeInfo.BlockSize, modeInfoPosition); modeInfo.SegmentId = this.FrameInfo.GetPredictedSegmentId(this.primaryReferenceFrameInfo, modeInfo.BlockSize, modeInfoPosition);
return; return;
} }
@ -3118,7 +3233,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
if (segmentId is < 0 || segmentId > lastActiveSegmentId) if (segmentId is < 0 || segmentId > lastActiveSegmentId)
{ {
// The coded alphabet always contains eight symbols, even when the frame activates fewer segments. // The coded alphabet always contains eight symbols, even when the frame activates fewer segments.
// Validate the reconstructed ID at the same corruption boundary as libaom's read_segment_id. // Validate the reconstructed ID at the same corruption boundary as the reference decoder's read_segment_id.
throw new InvalidImageContentException("The decoded AV1 segment identifier exceeds the active segment range."); throw new InvalidImageContentException("The decoded AV1 segment identifier exceeds the active segment range.");
} }
@ -3131,7 +3246,6 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param> /// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block.</param> /// <param name="partitionInfo">The current coding block.</param>
/// <remarks>Implements AV1 section 5.11.56 and corresponds to <c>read_cdef</c> in libaom.</remarks>
private void ReadCdef(ref Av1SymbolDecoder reader, ref Av1PartitionInfo partitionInfo) private void ReadCdef(ref Av1SymbolDecoder reader, ref Av1PartitionInfo partitionInfo)
{ {
if (partitionInfo.ModeInfo.Skip || this.FrameHeader.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameHeader.AllowIntraBlockCopy) if (partitionInfo.ModeInfo.Skip || this.FrameHeader.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameHeader.AllowIntraBlockCopy)
@ -3155,7 +3269,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
int lastUnitRow = (rowInSuperblock + blockHeight4 - 1) / cdefSize4; int lastUnitRow = (rowInSuperblock + blockHeight4 - 1) / cdefSize4;
int lastUnitColumn = (columnInSuperblock + blockWidth4 - 1) / cdefSize4; int lastUnitColumn = (columnInSuperblock + blockWidth4 - 1) / cdefSize4;
// A coding block can cover the top-left cell of more than one 64x64 CDEF unit. libaom // A coding block can cover the top-left cell of more than one 64x64 CDEF unit. the reference decoder
// stores the index on shared mode information, so the frame-owned unit map must mirror it. // stores the index on shared mode information, so the frame-owned unit map must mirror it.
for (int coveredUnitRow = unitRow; coveredUnitRow <= lastUnitRow; coveredUnitRow++) for (int coveredUnitRow = unitRow; coveredUnitRow <= lastUnitRow; coveredUnitRow++)
{ {
@ -3442,7 +3556,6 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param> /// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block and superblock quantizer storage.</param> /// <param name="partitionInfo">The current coding block and superblock quantizer storage.</param>
/// <remarks>Corresponds to <c>read_delta_qindex</c> in libaom.</remarks>
private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, ref Av1PartitionInfo partitionInfo) private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, ref Av1PartitionInfo partitionInfo)
{ {
if (!this.FrameHeader.DeltaQParameters.IsPresent || partitionInfo.ModeInfo.PositionInSuperblock != Point.Empty) if (!this.FrameHeader.DeltaQParameters.IsPresent || partitionInfo.ModeInfo.PositionInSuperblock != Point.Empty)
@ -3484,7 +3597,6 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
/// <param name="tileInfo">The active tile boundaries.</param> /// <param name="tileInfo">The active tile boundaries.</param>
/// <param name="superblockInfo">The containing superblock.</param> /// <param name="superblockInfo">The containing superblock.</param>
/// <returns>The partition entropy context.</returns> /// <returns>The partition entropy context.</returns>
/// <remarks>Corresponds to <c>partition_plane_context</c> in libaom.</remarks>
private int GetPartitionPlaneContext(Point location, Av1BlockSize blockSize, Av1TileInfo tileInfo, Av1SuperblockInfo superblockInfo) private int GetPartitionPlaneContext(Point location, Av1BlockSize blockSize, Av1TileInfo tileInfo, Av1SuperblockInfo superblockInfo)
{ {
// The five stored split bits begin at the 8x8 partition point, so normalize the block-size log to that bit index. // The five stored split bits begin at the 8x8 partition point, so normalize the block-size log to that bit index.

107
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs

@ -62,7 +62,6 @@ internal partial class Av1TileWriter
/// <param name="superblock">The encoder decisions for the superblock.</param> /// <param name="superblock">The encoder decisions for the superblock.</param>
/// <param name="frameBuffer">The transformed coefficients for the frame.</param> /// <param name="frameBuffer">The transformed coefficients for the frame.</param>
/// <param name="tileIndex">The zero-based tile index.</param> /// <param name="tileIndex">The zero-based tile index.</param>
/// <remarks>Corresponds to <c>svt_aom_write_sb</c> in SVT-AV1.</remarks>
public static void WriteSuperblock( public static void WriteSuperblock(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext ec_ctx, Av1EntropyCodingContext ec_ctx,
@ -112,39 +111,6 @@ internal partial class Av1TileWriter
if (bsize >= Av1BlockSize.Block8x8) if (bsize >= Av1BlockSize.Block8x8)
{ {
for (int plane = 0; plane < 3; ++plane)
{
/* TODO: Implement
if (svt_av1_loop_restoration_corners_in_sb(cm,
scs.SequenceHeader,
plane,
mi_row,
mi_col,
bsize,
out int rcol0,
out int rcol1,
out int rrow0,
out int rrow1,
out int tile_tl_idx))
{
int rstride = pcs.RestorationInfos[plane].HorizontalUnitCountPerTile;
for (int rrow = rrow0; rrow < rrow1; ++rrow)
{
for (int rcol = rcol0; rcol < rcol1; ++rcol)
{
int runit_idx = tile_tl_idx + rcol + (rrow * rstride);
Av1RestorationUnitInfo rui = pcs.RestorationUnitInfos[plane].UnitInfo[runit_idx];
loop_restoration_write_sb_coeffs(
pcs,
ref writer,
tileIndex,
rui,
plane);
}
}
}*/
}
// Blocks below 8x8 cannot be partition points in the AV1 syntax. // Blocks below 8x8 cannot be partition points in the AV1 syntax.
EncodePartition( EncodePartition(
pcs, pcs,
@ -307,7 +273,6 @@ internal partial class Av1TileWriter
/// <param name="partitionType">The selected partition type.</param> /// <param name="partitionType">The selected partition type.</param>
/// <param name="blockOrigin">The block origin in samples.</param> /// <param name="blockOrigin">The block origin in samples.</param>
/// <param name="partition_context_na">The partition neighbor arrays for the tile.</param> /// <param name="partition_context_na">The partition neighbor arrays for the tile.</param>
/// <remarks>Corresponds to <c>encode_partition_av1</c> in SVT-AV1.</remarks>
private static void EncodePartition( private static void EncodePartition(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
ref Av1SymbolEncoder writer, ref Av1SymbolEncoder writer,
@ -382,7 +347,6 @@ internal partial class Av1TileWriter
/// <param name="blk_ptr">The final encoder decisions for the block.</param> /// <param name="blk_ptr">The final encoder decisions for the block.</param>
/// <param name="tile_idx">The zero-based tile index.</param> /// <param name="tile_idx">The zero-based tile index.</param>
/// <param name="coeff_ptr">The transformed coefficients for the frame.</param> /// <param name="coeff_ptr">The transformed coefficients for the frame.</param>
/// <remarks>Corresponds to <c>write_modes_b</c> in SVT-AV1.</remarks>
private static void WriteModesBlock( private static void WriteModesBlock(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext, Av1EntropyCodingContext entropyCodingContext,
@ -539,26 +503,6 @@ internal partial class Av1TileWriter
if (palette_size_plane > 0) if (palette_size_plane > 0)
{ {
throw new NotImplementedException("Tokenizing palette not implemented."); throw new NotImplementedException("Tokenizing palette not implemented.");
/*
Av1TransformSize tx_size =
blockGeometry.TransformSize[macroBlockModeInfo.Block.TransformDepth]; // inherit tx_size from 1st transform block;
svt_av1_tokenize_color_map(
frame_context,
blk_ptr,
plane,
tok,
blockSize,
tx_size,
PALETTE_MAP,
0); // NO CDF update in entropy, the update will take place in arithmetic encode
assert(macroBlockModeInfo.Block.UseIntraBlockCopy);
assert(IsPaletteAllowed(pcs.Parent.FrameHeader.AllowScreenContentTools, blockGeometry.BlockSize));
svt_aom_get_block_dimensions(blockGeometry.BlockSize, plane, blk_ptr.MacroBlock, null, null, out int rowCount, out int columnCount);
pack_map_tokens(ref writer, ref entropyCodingContext.tok, palette_size_plane, rowCount * columnCount);
// advance the pointer
entropyCodingContext.tok = tok;
*/
} }
} }
} }
@ -650,7 +594,6 @@ internal partial class Av1TileWriter
/// <param name="xd">The current macroblock and its mapped neighbors.</param> /// <param name="xd">The current macroblock and its mapped neighbors.</param>
/// <param name="above_ctx">The context derived from the above luma mode.</param> /// <param name="above_ctx">The context derived from the above luma mode.</param>
/// <param name="left_ctx">The context derived from the left luma mode.</param> /// <param name="left_ctx">The context derived from the left luma mode.</param>
/// <remarks>Corresponds to <c>svt_aom_get_kf_y_mode_ctx</c> in SVT-AV1.</remarks>
private static void GetYModeContext(Av1MacroBlockD xd, out byte above_ctx, out byte left_ctx) private static void GetYModeContext(Av1MacroBlockD xd, out byte above_ctx, out byte left_ctx)
{ {
Av1PredictionMode intraLumaLeftMode = Av1PredictionMode.DC; Av1PredictionMode intraLumaLeftMode = Av1PredictionMode.DC;
@ -678,7 +621,6 @@ internal partial class Av1TileWriter
/// <param name="blk_ptr">The encoder prediction-unit state.</param> /// <param name="blk_ptr">The encoder prediction-unit state.</param>
/// <param name="blockSize">The block size.</param> /// <param name="blockSize">The block size.</param>
/// <param name="lumaMode">The selected luma prediction mode.</param> /// <param name="lumaMode">The selected luma prediction mode.</param>
/// <remarks>Corresponds to <c>encode_intra_luma_mode_kf_av1</c> in SVT-AV1.</remarks>
private static void EncodeIntraLumaMode( private static void EncodeIntraLumaMode(
ref Av1SymbolEncoder writer, ref Av1SymbolEncoder writer,
Av1MacroBlockModeInfo macroBlockModeInfo, Av1MacroBlockModeInfo macroBlockModeInfo,
@ -712,32 +654,7 @@ internal partial class Av1TileWriter
Av1EncoderBlockStruct blk_ptr, Av1EncoderBlockStruct blk_ptr,
Av1BlockSize blockSize, Av1BlockSize blockSize,
Point point) Point point)
{/* {
Av1PredictionMode intra_luma_mode = macroBlockModeInfo.Mode;
Av1ChromaPredictionMode intra_chroma_mode = macroBlockModeInfo.Block.UvMode;
Av1PaletteModeInfo pmi = blk_ptr.PaletteInfo.pmi;
int bsize_ctx = svt_aom_get_palette_bsize_ctx(bsize);
Guard.MustBeGreaterThanOrEqualTo(bsize_ctx, 0, nameof(bsize_ctx));
if (intra_luma_mode == Av1PredictionMode.DC)
{
int n = blk_ptr.PaletteSize[0];
int palette_y_mode_ctx = svt_aom_get_palette_mode_ctx(blk_ptr->av1xd);
writer.WriteYMode(n > 0, bsize_ctx, palette_y_mode_ctx);
if (n > 0)
{
writer.WriteYSize(n - PALETTE_MIN_SIZE, bsize_ctx);
write_palette_colors_y(blk_ptr.MacroBlock, pmi, scs.StaticConfig.EncoderBitDepth, ref writer, n);
}
}
bool uv_dc_pred = intra_chroma_mode == Av1ChromaPredictionMode.DC && is_chroma_reference(point, blockSize, 1, 1);
if (uv_dc_pred)
{
// assert(blk_ptr->palette_size[1] == 0); //remove when chroma is on
bool palette_uv_mode_ctx = blk_ptr.PaletteSize[0] > 0;
writer.WriteUvMode(false, palette_uv_mode_ctx);
}*/
throw new NotImplementedException("Palette mode encoding not implemented."); throw new NotImplementedException("Palette mode encoding not implemented.");
} }
@ -749,7 +666,6 @@ internal partial class Av1TileWriter
/// <param name="paletteSize">The selected luma palette size.</param> /// <param name="paletteSize">The selected luma palette size.</param>
/// <param name="mode">The selected luma prediction mode.</param> /// <param name="mode">The selected luma prediction mode.</param>
/// <returns><see langword="true"/> when the block can use filter-intra prediction; otherwise, <see langword="false"/>.</returns> /// <returns><see langword="true"/> when the block can use filter-intra prediction; otherwise, <see langword="false"/>.</returns>
/// <remarks>Corresponds to <c>svt_aom_filter_intra_allowed</c> in SVT-AV1.</remarks>
private static bool IsFilterIntraAllowed( private static bool IsFilterIntraAllowed(
bool enableFilterIntra, bool enableFilterIntra,
Av1BlockSize blockSize, Av1BlockSize blockSize,
@ -763,7 +679,6 @@ internal partial class Av1TileWriter
/// <param name="enableFilterIntra">A value indicating whether the sequence enables filter-intra prediction.</param> /// <param name="enableFilterIntra">A value indicating whether the sequence enables filter-intra prediction.</param>
/// <param name="blockSize">The block size.</param> /// <param name="blockSize">The block size.</param>
/// <returns><see langword="true"/> when filter-intra prediction supports the block dimensions; otherwise, <see langword="false"/>.</returns> /// <returns><see langword="true"/> when filter-intra prediction supports the block dimensions; otherwise, <see langword="false"/>.</returns>
/// <remarks>Corresponds to <c>svt_aom_filter_intra_allowed_bsize</c> in SVT-AV1.</remarks>
private static bool IsFilterIntraAllowedBlockSize(bool enableFilterIntra, Av1BlockSize blockSize) private static bool IsFilterIntraAllowedBlockSize(bool enableFilterIntra, Av1BlockSize blockSize)
{ {
if (!enableFilterIntra) if (!enableFilterIntra)
@ -781,7 +696,6 @@ internal partial class Av1TileWriter
/// <param name="macroBlockModeInfo">The selected block modes.</param> /// <param name="macroBlockModeInfo">The selected block modes.</param>
/// <param name="block">The encoder block state.</param> /// <param name="block">The encoder block state.</param>
/// <exception cref="NotImplementedException">The displacement-vector syntax is not implemented when intra block copy is selected.</exception> /// <exception cref="NotImplementedException">The displacement-vector syntax is not implemented when intra block copy is selected.</exception>
/// <remarks>Corresponds to <c>write_intrabc_info</c> in SVT-AV1.</remarks>
private static void WriteIntraBlockCopyInfo( private static void WriteIntraBlockCopyInfo(
ref Av1SymbolEncoder writer, ref Av1SymbolEncoder writer,
Av1MacroBlockModeInfo macroBlockModeInfo, Av1MacroBlockModeInfo macroBlockModeInfo,
@ -792,14 +706,6 @@ internal partial class Av1TileWriter
if (use_intrabc) if (use_intrabc)
{ {
throw new NotImplementedException("Intra block code encoding not implemented."); throw new NotImplementedException("Intra block code encoding not implemented.");
/*
//assert(mbmi->mode == DC_PRED);
//assert(mbmi->uv_mode == UV_DC_PRED);
//assert(mbmi->motion_mode == SIMPLE_TRANSLATION);
IntMv dv_ref = block->predmv[0]; // mbmi_ext->ref_mv_stack[INTRA_FRAME][0].this_mv;
MV mv;
mv = macroBlockModeInfo.Block.mv[INTRA_FRAME].as_mv;
svt_av1_encode_dv(w, &mv, &dv_ref.as_mv, &ec_ctx->ndvc);*/
} }
} }
@ -808,7 +714,6 @@ internal partial class Av1TileWriter
/// </summary> /// </summary>
/// <param name="frameHeader">The current frame header.</param> /// <param name="frameHeader">The current frame header.</param>
/// <returns><see langword="true"/> when both screen-content tools and intra block copy are enabled; otherwise, <see langword="false"/>.</returns> /// <returns><see langword="true"/> when both screen-content tools and intra block copy are enabled; otherwise, <see langword="false"/>.</returns>
/// <remarks>Corresponds to <c>svt_aom_allow_intrabc</c> in SVT-AV1.</remarks>
private static bool IsIntraBlockCopyAllowed(ObuFrameHeader frameHeader) private static bool IsIntraBlockCopyAllowed(ObuFrameHeader frameHeader)
=> frameHeader.AllowScreenContentTools && frameHeader.AllowIntraBlockCopy; => frameHeader.AllowScreenContentTools && frameHeader.AllowIntraBlockCopy;
@ -821,7 +726,6 @@ internal partial class Av1TileWriter
/// <param name="blk_ptr">The encoder block state.</param> /// <param name="blk_ptr">The encoder block state.</param>
/// <param name="tile_idx">The zero-based tile index.</param> /// <param name="tile_idx">The zero-based tile index.</param>
/// <param name="blockSize">The block size.</param> /// <param name="blockSize">The block size.</param>
/// <remarks>Corresponds to <c>ec_update_neighbors</c> in SVT-AV1.</remarks>
private static void UpdateNeighbors( private static void UpdateNeighbors(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext, Av1EntropyCodingContext entropyCodingContext,
@ -887,7 +791,6 @@ internal partial class Av1TileWriter
/// <param name="allowPalette">The nonzero encoder palette level.</param> /// <param name="allowPalette">The nonzero encoder palette level.</param>
/// <param name="blockSize">The block size.</param> /// <param name="blockSize">The block size.</param>
/// <returns><see langword="true"/> when palette mode is enabled for the block; otherwise, <see langword="false"/>.</returns> /// <returns><see langword="true"/> when palette mode is enabled for the block; otherwise, <see langword="false"/>.</returns>
/// <remarks>Corresponds to <c>svt_av1_allow_palette</c> in SVT-AV1.</remarks>
private static bool IsPaletteAllowed(int allowPalette, Av1BlockSize blockSize) private static bool IsPaletteAllowed(int allowPalette, Av1BlockSize blockSize)
{ {
Guard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.AllSizes, nameof(blockSize)); Guard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.AllSizes, nameof(blockSize));
@ -903,7 +806,6 @@ internal partial class Av1TileWriter
/// <param name="allowScreenContentTools">A value indicating whether screen-content tools are enabled.</param> /// <param name="allowScreenContentTools">A value indicating whether screen-content tools are enabled.</param>
/// <param name="blockSize">The block size.</param> /// <param name="blockSize">The block size.</param>
/// <returns><see langword="true"/> when palette mode is available for the block; otherwise, <see langword="false"/>.</returns> /// <returns><see langword="true"/> when palette mode is available for the block; otherwise, <see langword="false"/>.</returns>
/// <remarks>Corresponds to <c>svt_aom_allow_palette</c> in SVT-AV1.</remarks>
private static bool IsPaletteAllowed(bool allowScreenContentTools, Av1BlockSize blockSize) private static bool IsPaletteAllowed(bool allowScreenContentTools, Av1BlockSize blockSize)
=> allowScreenContentTools && => allowScreenContentTools &&
blockSize.GetWidth() <= 64 && blockSize.GetWidth() <= 64 &&
@ -919,7 +821,6 @@ internal partial class Av1TileWriter
/// <param name="tileIndex">The zero-based tile index.</param> /// <param name="tileIndex">The zero-based tile index.</param>
/// <param name="skip">A value indicating whether the current block omits residual coefficients.</param> /// <param name="skip">A value indicating whether the current block omits residual coefficients.</param>
/// <param name="modeInfoPosition">The block position in 4x4 mode-information units.</param> /// <param name="modeInfoPosition">The block position in 4x4 mode-information units.</param>
/// <remarks>Corresponds to <c>write_cdef</c> in SVT-AV1.</remarks>
private static void WriteCdef( private static void WriteCdef(
Av1SequenceControlSet scs, Av1SequenceControlSet scs,
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
@ -978,7 +879,6 @@ internal partial class Av1TileWriter
/// <param name="modeInfoStride">The row stride of the mode-information grid.</param> /// <param name="modeInfoStride">The row stride of the mode-information grid.</param>
/// <param name="modeInfoRowCount">The coded frame height in mode-information rows.</param> /// <param name="modeInfoRowCount">The coded frame height in mode-information rows.</param>
/// <param name="modeInfoColumnCount">The coded frame width in mode-information columns.</param> /// <param name="modeInfoColumnCount">The coded frame width in mode-information columns.</param>
/// <remarks>Corresponds to <c>set_mi_row_col</c> in SVT-AV1.</remarks>
private static void SetModeInfoRowAndColumn( private static void SetModeInfoRowAndColumn(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
@ -1056,7 +956,6 @@ internal partial class Av1TileWriter
/// <param name="cr_dc_sign_level_coeff_na">The red-difference chroma coefficient neighbor contexts.</param> /// <param name="cr_dc_sign_level_coeff_na">The red-difference chroma coefficient neighbor contexts.</param>
/// <param name="cb_dc_sign_level_coeff_na">The blue-difference chroma coefficient neighbor contexts.</param> /// <param name="cb_dc_sign_level_coeff_na">The blue-difference chroma coefficient neighbor contexts.</param>
/// <exception cref="NotImplementedException">The transform-depth path required by the block is not implemented.</exception> /// <exception cref="NotImplementedException">The transform-depth path required by the block is not implemented.</exception>
/// <remarks>Corresponds to <c>av1_encode_coeff_1d</c> in SVT-AV1.</remarks>
private static void EncodeCoefficients1d( private static void EncodeCoefficients1d(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext ec_ctx, Av1EntropyCodingContext ec_ctx,
@ -1117,7 +1016,6 @@ internal partial class Av1TileWriter
/// <param name="plane_bsize">The luma block size.</param> /// <param name="plane_bsize">The luma block size.</param>
/// <param name="coeff_ptr">The transformed coefficients for the frame.</param> /// <param name="coeff_ptr">The transformed coefficients for the frame.</param>
/// <param name="luma_dc_sign_level_coeff_na">The luma coefficient neighbor contexts.</param> /// <param name="luma_dc_sign_level_coeff_na">The luma coefficient neighbor contexts.</param>
/// <remarks>Corresponds to <c>av1_encode_tx_coef_y</c> in SVT-AV1.</remarks>
public static void EncodeTransformCoefficientsY( public static void EncodeTransformCoefficientsY(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext, Av1EntropyCodingContext entropyCodingContext,
@ -1207,7 +1105,6 @@ internal partial class Av1TileWriter
/// <param name="coeff_ptr">The transformed coefficients for the frame.</param> /// <param name="coeff_ptr">The transformed coefficients for the frame.</param>
/// <param name="cr_dc_sign_level_coeff_na">The red-difference chroma coefficient neighbor contexts.</param> /// <param name="cr_dc_sign_level_coeff_na">The red-difference chroma coefficient neighbor contexts.</param>
/// <param name="cb_dc_sign_level_coeff_na">The blue-difference chroma coefficient neighbor contexts.</param> /// <param name="cb_dc_sign_level_coeff_na">The blue-difference chroma coefficient neighbor contexts.</param>
/// <remarks>Corresponds to <c>av1_encode_tx_coef_uv</c> in SVT-AV1.</remarks>
private static void EncodeTransformCoefficientsUv( private static void EncodeTransformCoefficientsUv(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext, Av1EntropyCodingContext entropyCodingContext,
@ -1328,7 +1225,6 @@ internal partial class Av1TileWriter
/// <param name="planeBlockSize">The containing block size on the target plane.</param> /// <param name="planeBlockSize">The containing block size on the target plane.</param>
/// <param name="transformSize">The transform size.</param> /// <param name="transformSize">The transform size.</param>
/// <param name="blockContext">The context object to populate.</param> /// <param name="blockContext">The context object to populate.</param>
/// <remarks>Corresponds to <c>svt_aom_get_txb_ctx</c> in SVT-AV1.</remarks>
private static void GetTransformBlockContexts( private static void GetTransformBlockContexts(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1ComponentType plane, Av1ComponentType plane,
@ -1514,7 +1410,6 @@ internal partial class Av1TileWriter
/// <param name="blockOrigin">The block origin in samples.</param> /// <param name="blockOrigin">The block origin in samples.</param>
/// <param name="cdf_index">The entropy context selected by matching neighbor identifiers.</param> /// <param name="cdf_index">The entropy context selected by matching neighbor identifiers.</param>
/// <returns>The spatially predicted segment identifier.</returns> /// <returns>The spatially predicted segment identifier.</returns>
/// <remarks>Corresponds to <c>svt_av1_get_spatial_seg_prediction</c> in SVT-AV1.</remarks>
private static int GetSpatialSegmentationPrediction( private static int GetSpatialSegmentationPrediction(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1MacroBlockD xd, Av1MacroBlockD xd,

18
src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs

@ -364,7 +364,7 @@ internal sealed class Av1BlockDecoder : IDisposable
int referenceCount = usesSub8x8ChromaPrediction ? 0 : isCompound ? 2 : 1; int referenceCount = usesSub8x8ChromaPrediction ? 0 : isCompound ? 2 : 1;
// Every compound predictor is combined before its final rounding step. Warped prediction has its own // Every compound predictor is combined before its final rounding step. Warped prediction has its own
// convolution kernels, but current libaom writes their output into the same unsigned no-round domain. // convolution kernels, but the reference decoder writes their output into the same unsigned no-round domain.
bool useHighBitDepthCompoundIntermediates = bool useHighBitDepthCompoundIntermediates =
highBitDepth && highBitDepth &&
modeInfo.CompoundType is ( modeInfo.CompoundType is (
@ -391,7 +391,7 @@ internal sealed class Av1BlockDecoder : IDisposable
activeReferenceFrameBuffer.Height != this.frameHeader.FrameSize.FrameHeight; activeReferenceFrameBuffer.Height != this.frameHeader.FrameSize.FrameHeight;
// Warped prediction is selected per plane. In subsampled frames an otherwise qualifying 8x8 luma // Warped prediction is selected per plane. In subsampled frames an otherwise qualifying 8x8 luma
// block has a 4x4 chroma prediction, which libaom deliberately reconstructs with the translational // block has a 4x4 chroma prediction, which the reference decoder deliberately reconstructs with the translational
// center motion vector. Scaled references and integer-only frames exclude both local and global warp. // center motion vector. Scaled references and integer-only frames exclude both local and global warp.
bool canUseWarpedPrediction = bool canUseWarpedPrediction =
!isScaledReference && !isScaledReference &&
@ -594,7 +594,7 @@ internal sealed class Av1BlockDecoder : IDisposable
} }
// AV1 predicts the complete declared plane block even when its luma extent crosses the frame boundary. // AV1 predicts the complete declared plane block even when its luma extent crosses the frame boundary.
// Subsampled dimensions retain the mandatory four-sample minimum used by set_plane_n4 in libaom. // Subsampled dimensions retain the mandatory four-sample minimum used by set_plane_n4 in the reference decoder.
int horizontalMotionQ4 = motionVector.Column << (1 - subX); int horizontalMotionQ4 = motionVector.Column << (1 - subX);
int verticalMotionQ4 = motionVector.Row << (1 - subY); int verticalMotionQ4 = motionVector.Row << (1 - subY);
int horizontalExtensionQ4 = (4 + predictionWidth) << 4; int horizontalExtensionQ4 = (4 + predictionWidth) << 4;
@ -773,7 +773,7 @@ internal sealed class Av1BlockDecoder : IDisposable
subY, subY,
invert: false); invert: false);
// Masked compound prediction must blend the same no-round intermediates as libaom's // Masked compound prediction must blend the same no-round intermediates as the reference decoder's
// high-bit-depth d16 path so the mask is applied before the sole final rounding step. // high-bit-depth d16 path so the mask is applied before the sole final rounding step.
Av1CompoundIntermediateMaskBlendPredictor.BlendIntermediate( Av1CompoundIntermediateMaskBlendPredictor.BlendIntermediate(
highBitDepthDestination, highBitDepthDestination,
@ -1240,7 +1240,7 @@ internal sealed class Av1BlockDecoder : IDisposable
// rules. Its validated displacement always references an earlier reconstructed region of this frame. // rules. Its validated displacement always references an earlier reconstructed region of this frame.
if (modeInfo.UseIntraBlockCopy) if (modeInfo.UseIntraBlockCopy)
{ {
// libaom predicts the complete coding block before traversing its residual transforms. The mandatory // the reference decoder predicts the complete coding block before traversing its residual transforms. The mandatory
// 256-pixel source delay prevents overlap, and the two-tap interpolation is translation-invariant, // 256-pixel source delay prevents overlap, and the two-tap interpolation is translation-invariant,
// so predicting the matching source rectangle for each transform unit produces the same samples. // so predicting the matching source rectangle for each transform unit produces the same samples.
Point transformPixelPosition = new( Point transformPixelPosition = new(
@ -1452,7 +1452,7 @@ internal sealed class Av1BlockDecoder : IDisposable
int rowStart = isSub4Y ? -1 : 0; int rowStart = isSub4Y ? -1 : 0;
int columnStart = isSub4X ? -1 : 0; int columnStart = isSub4X ? -1 : 0;
// One chroma block can cover two or four independently decoded luma blocks. libaom enters this path only // One chroma block can cover two or four independently decoded luma blocks. the reference decoder enters this path only
// when every contributing owner is a conventional inter block; otherwise the current block supplies the // when every contributing owner is a conventional inter block; otherwise the current block supplies the
// complete chroma prediction through the ordinary path. // complete chroma prediction through the ordinary path.
for (int row = rowStart; row <= 0; row++) for (int row = rowStart; row <= 0; row++)
@ -1534,7 +1534,7 @@ internal sealed class Av1BlockDecoder : IDisposable
// The block-relative UMV edges belong to the current coding block, while each contributing luma // The block-relative UMV edges belong to the current coding block, while each contributing luma
// owner supplies only its motion vector and interpolation filters. This is the same split used by // owner supplies only its motion vector and interpolation filters. This is the same split used by
// libaom's sub-8x8 chroma builder. // the reference decoder's sub-8x8 chroma builder.
horizontalMotionQ4 = Av1Math.Clip3( horizontalMotionQ4 = Av1Math.Clip3(
(partitionInfo.ModeBlockToLeftEdge * horizontalEdgeScale) - horizontalExtensionQ4, (partitionInfo.ModeBlockToLeftEdge * horizontalEdgeScale) - horizontalExtensionQ4,
(partitionInfo.ModeBlockToRightEdge * horizontalEdgeScale) + horizontalExtensionQ4 - 16, (partitionInfo.ModeBlockToRightEdge * horizontalEdgeScale) + horizontalExtensionQ4 - 16,
@ -1830,7 +1830,7 @@ internal sealed class Av1BlockDecoder : IDisposable
int step = Math.Min(candidate.BlockSize.Get4x4WideCount(), Av1BlockSize.Block64x64.Get4x4WideCount()); int step = Math.Min(candidate.BlockSize.Get4x4WideCount(), Av1BlockSize.Block64x64.Get4x4WideCount());
if (step == 1) if (step == 1)
{ {
// A four-sample neighbor is one half of the chroma-bearing eight-sample pair. libaom aligns // A four-sample neighbor is one half of the chroma-bearing eight-sample pair. the reference decoder aligns
// the traversal to the pair start and reads prediction state from its second mode record. // the traversal to the pair start and reads prediction state from its second mode record.
aboveColumn &= ~1; aboveColumn &= ~1;
candidate = partitionInfo.SuperblockInfo.GetModeInfoAt(new Point(aboveColumn + 1, blockRow - 1)); candidate = partitionInfo.SuperblockInfo.GetModeInfoAt(new Point(aboveColumn + 1, blockRow - 1));
@ -2034,7 +2034,7 @@ internal sealed class Av1BlockDecoder : IDisposable
int framePlaneWidth = (this.frameHeader.ModeInfoColumnCount << Av1Constants.ModeInfoSizeLog2) >> subX; int framePlaneWidth = (this.frameHeader.ModeInfoColumnCount << Av1Constants.ModeInfoSizeLog2) >> subX;
int framePlaneHeight = (this.frameHeader.ModeInfoRowCount << Av1Constants.ModeInfoSizeLog2) >> subY; int framePlaneHeight = (this.frameHeader.ModeInfoRowCount << Av1Constants.ModeInfoSizeLog2) >> subY;
// libaom clamps the motion vector relative to each neighbor rectangle. Once the neighbor origin is added, the // the reference decoder clamps the motion vector relative to each neighbor rectangle. Once the neighbor origin is added, the
// prediction extent remains in the left/top limit but cancels from the right/bottom limit. Keeping this // prediction extent remains in the left/top limit but cancels from the right/bottom limit. Keeping this
// asymmetry avoids counting the OBMC rectangle twice when the source lies beyond the far frame edge. // asymmetry avoids counting the OBMC rectangle twice when the source lies beyond the far frame edge.
sourceColumnQ4 = Av1Math.Clip3( sourceColumnQ4 = Av1Math.Clip3(

10
src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs

@ -189,7 +189,7 @@ internal static partial class Av1ForwardTransformer
where TRowOperator : struct, IAv1ForwardTransform1dOperator where TRowOperator : struct, IAv1ForwardTransform1dOperator
{ {
// Highway keeps eight-bit transform stages in Int16 lanes and promotes only the large rectangular layouts. // Highway keeps eight-bit transform stages in Int16 lanes and promotes only the large rectangular layouts.
// Scalar libaom uses Int32, so hardware without packed Int16 support follows that exact fallback instead. // The independent scalar reference uses Int32, so hardware without packed Int16 support follows that exact fallback instead.
if (bitDepth == 8 && Vector128.IsHardwareAccelerated) if (bitDepth == 8 && Vector128.IsHardwareAccelerated)
{ {
TransformPacked<TColumnOperator, TRowOperator>(input, coefficients, stride, ref config, workspace); TransformPacked<TColumnOperator, TRowOperator>(input, coefficients, stride, ref config, workspace);
@ -200,7 +200,7 @@ internal static partial class Av1ForwardTransformer
} }
/// <summary> /// <summary>
/// Applies the libaom Int16 stage pipeline used for eight-bit residuals. /// Applies the signed Int16 stage pipeline used for eight-bit residuals.
/// </summary> /// </summary>
/// <typeparam name="TColumnOperator">The column transform operator.</typeparam> /// <typeparam name="TColumnOperator">The column transform operator.</typeparam>
/// <typeparam name="TRowOperator">The row transform operator.</typeparam> /// <typeparam name="TRowOperator">The row transform operator.</typeparam>
@ -298,7 +298,7 @@ internal static partial class Av1ForwardTransformer
TransformPackedAxis<TRowOperator>(buffer1Packed, height, blockHeight, blockHeight, config.CosBitRow, workspace); TransformPackedAxis<TRowOperator>(buffer1Packed, height, blockHeight, blockHeight, config.CosBitRow, workspace);
// The second transform produces horizontal frequency in rows and vertical frequency in lanes. Transposing // The second transform produces horizontal frequency in rows and vertical frequency in lanes. Transposing
// once more adapts libaom's native layout to the row-major coefficient contract used by ImageSharp. // once more adapts the reference decoder's native layout to the row-major coefficient contract used by ImageSharp.
TransposePacked( TransposePacked(
ref buffer1PackedBase, ref buffer1PackedBase,
blockHeight, blockHeight,
@ -314,7 +314,7 @@ internal static partial class Av1ForwardTransformer
} }
/// <summary> /// <summary>
/// Applies the libaom Int32 stage pipeline used for high-bit-depth residuals and scalar fallback. /// Applies the signed Int32 stage pipeline used for high-bit-depth residuals and scalar fallback.
/// </summary> /// </summary>
/// <typeparam name="TColumnOperator">The column transform operator.</typeparam> /// <typeparam name="TColumnOperator">The column transform operator.</typeparam>
/// <typeparam name="TRowOperator">The row transform operator.</typeparam> /// <typeparam name="TRowOperator">The row transform operator.</typeparam>
@ -881,7 +881,7 @@ internal static partial class Av1ForwardTransformer
} }
/// <summary> /// <summary>
/// Promotes and transposes the large packed layouts at the same axis boundary as libaom. /// Promotes and transposes the large packed layouts at the same axis boundary as the reference decoder.
/// </summary> /// </summary>
/// <param name="source">The first packed value in the source block.</param> /// <param name="source">The first packed value in the source block.</param>
/// <param name="sourceStride">The number of packed values between source rows.</param> /// <param name="sourceStride">The number of packed values between source rows.</param>

2
src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseWalshHadamardTransformer.cs

@ -206,7 +206,7 @@ internal static class Av1InverseWalshHadamardTransformer
ref int intermediateBase = ref MemoryMarshal.GetReference(workspace); ref int intermediateBase = ref MemoryMarshal.GetReference(workspace);
// Entropy decoding stores the transposed scan in row-major order, so each contiguous local row is one // Entropy decoding stores the transposed scan in row-major order, so each contiguous local row is one
// normative transform column. Writing those results down the intermediate columns preserves libaom's // normative transform column. Writing those results down the intermediate columns preserves the reference decoder's
// dimension order without a separate transpose or per-block allocation. // dimension order without a separate transpose or per-block allocation.
for (int row = 0; row < 4; row++) for (int row = 0; row < 4; row++)
{ {

8
src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform1dMath.cs

@ -132,7 +132,7 @@ internal static class Av1Transform1dMath
out Vector512<short> sum, out Vector512<short> sum,
out Vector512<short> difference) out Vector512<short> difference)
{ {
// Read both operands before either destination is written because libaom deliberately permits an input // Read both operands before either destination is written because the reference decoder deliberately permits an input
// buffer to alias one or both outputs while alternating between its two fixed transform-stage buffers. // buffer to alias one or both outputs while alternating between its two fixed transform-stage buffers.
Vector512<short> left = input0; Vector512<short> left = input0;
Vector512<short> right = input1; Vector512<short> right = input1;
@ -164,7 +164,7 @@ internal static class Av1Transform1dMath
in Vector512<int> rounding) in Vector512<int> rounding)
{ {
// VPMADDWD evaluates adjacent Int16 products into Int32 lanes. Both outputs reuse the same interleaved // VPMADDWD evaluates adjacent Int16 products into Int32 lanes. Both outputs reuse the same interleaved
// inputs, matching libaom's whole butterfly instead of loading and unpacking each input pair twice. // inputs, matching the reference decoder's whole butterfly instead of loading and unpacking each input pair twice.
Vector512<short> left = input0; Vector512<short> left = input0;
Vector512<short> right = input1; Vector512<short> right = input1;
Vector512<short> interleavedLower = Avx512BW.UnpackLow(left, right); Vector512<short> interleavedLower = Avx512BW.UnpackLow(left, right);
@ -414,7 +414,7 @@ internal static class Av1Transform1dMath
(Vector128<long> lower, Vector128<long> upper) = Vector128.Widen(value); (Vector128<long> lower, Vector128<long> upper) = Vector128.Widen(value);
Vector128<long> rounding = Vector128.Create(1L << (fractionalBits - 1)); Vector128<long> rounding = Vector128.Create(1L << (fractionalBits - 1));
// Pinned libaom's high-bit-depth identity kernels multiply in signed 64-bit lanes. Widen before both the // The reference decoder's high-bit-depth identity kernels multiply in signed 64-bit lanes. Widen before both the
// product and rounding addition so a valid 20-bit twelve-bit row value cannot wrap through Int32. // product and rounding addition so a valid 20-bit twelve-bit row value cannot wrap through Int32.
lower = ((lower * multiplier) + rounding) >> fractionalBits; lower = ((lower * multiplier) + rounding) >> fractionalBits;
upper = ((upper * multiplier) + rounding) >> fractionalBits; upper = ((upper * multiplier) + rounding) >> fractionalBits;
@ -573,7 +573,7 @@ internal static class Av1Transform1dMath
Vector128<int> input3, Vector128<int> input3,
int fractionalBits) int fractionalBits)
{ {
// libaom keeps conformant ADST4 sine products and their factorized sums in Int32, then widens the terminal // the reference decoder keeps conformant ADST4 sine products and their factorized sums in Int32, then widens the terminal
// scaling and rounding. Preserve that exact boundary instead of widening every transform multiplication. // scaling and rounding. Preserve that exact boundary instead of widening every transform multiplication.
Vector128<int> weightedSum = (input0 * weight0) + (input1 * weight1) + (input2 * weight2) + (input3 * weight3); Vector128<int> weightedSum = (input0 * weight0) + (input1 * weight1) + (input2 * weight2) + (input3 * weight3);
return MultiplyRoundWidened(weightedSum, 1, fractionalBits); return MultiplyRoundWidened(weightedSum, 1, fractionalBits);

18
src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dOperations.cs

@ -144,7 +144,7 @@ internal static class Av1Transform2dOperations
if (bit > 0) if (bit > 0)
{ {
// Conformant low-bit-depth stage ranges leave room for the rounding bias, so this intentionally uses the // Conformant low-bit-depth stage ranges leave room for the rounding bias, so this intentionally uses the
// wrapping add used by libaom rather than changing the normative result with a saturating instruction. // wrapping add used by the reference decoder rather than changing the normative result with a saturating instruction.
return (value + Vector256.Create((short)(1 << (bit - 1)))) >> bit; return (value + Vector256.Create((short)(1 << (bit - 1)))) >> bit;
} }
@ -382,7 +382,7 @@ internal static class Av1Transform2dOperations
} }
// Once promoted, the same bounded transpose used by the high-bit-depth AVX-512 path supplies the exact // Once promoted, the same bounded transpose used by the high-bit-depth AVX-512 path supplies the exact
// libaom staging order and performs the axis-boundary shift in signed thirty-two-bit lanes. // the reference decoder staging order and performs the axis-boundary shift in signed thirty-two-bit lanes.
Transpose16x16Avx512( Transpose16x16Avx512(
ref promotionBase, ref promotionBase,
16, 16,
@ -415,7 +415,7 @@ internal static class Av1Transform2dOperations
ref int promotionBase = ref MemoryMarshal.GetReference(promotionBuffer); ref int promotionBase = ref MemoryMarshal.GetReference(promotionBuffer);
// AVX2 processes eight Int32 transform axes at a time. Widening each packed row before the axis shift follows // AVX2 processes eight Int32 transform axes at a time. Widening each packed row before the axis shift follows
// libaom's Repartition<int32_t> boundary and prevents valid Int32 intermediates from wrapping in Int16. // the reference decoder's Repartition<int32_t> boundary and prevents valid Int32 intermediates from wrapping in Int16.
for (int row = 0; row < 8; row++) for (int row = 0; row < 8; row++)
{ {
Vector128<short> packed = Vector128.LoadUnsafe(ref source, (nuint)(row * sourceStride)); Vector128<short> packed = Vector128.LoadUnsafe(ref source, (nuint)(row * sourceStride));
@ -663,7 +663,7 @@ internal static class Av1Transform2dOperations
} }
/// <summary> /// <summary>
/// Transposes a sixteen-by-sixteen matrix of signed thirty-two-bit values with the libaom AVX-512 staging layout. /// Transposes a sixteen-by-sixteen matrix of signed thirty-two-bit values with the AVX-512 staging layout.
/// </summary> /// </summary>
/// <param name="source">The first value in the source matrix.</param> /// <param name="source">The first value in the source matrix.</param>
/// <param name="sourceStride">The number of values between source rows.</param> /// <param name="sourceStride">The number of values between source rows.</param>
@ -683,7 +683,7 @@ internal static class Av1Transform2dOperations
{ {
ref long scratchBase = ref MemoryMarshal.GetReference(scratch); ref long scratchBase = ref MemoryMarshal.GetReference(scratch);
// libaom widens the lane grouping after each local interleave rather than retaining all sixteen rows in // the reference decoder widens the lane grouping after each local interleave rather than retaining all sixteen rows in
// registers. The bounded scratch keeps the live register set small and prevents the JIT from spilling a // registers. The bounded scratch keeps the live register set small and prevents the JIT from spilling a
// four-stage, sixteen-register cross-vector permutation network into its own stack frame. // four-stage, sixteen-register cross-vector permutation network into its own stack frame.
for (int row = 0; row < 16; row += 2) for (int row = 0; row < 16; row += 2)
@ -733,7 +733,7 @@ internal static class Av1Transform2dOperations
} }
// The final 128-bit-block concatenations complete the transpose. The store order is the fixed permutation // The final 128-bit-block concatenations complete the transpose. The store order is the fixed permutation
// produced by libaom's three preceding local-interleave stages. // produced by the reference decoder's three preceding local-interleave stages.
for (int row = 0; row < 8; row++) for (int row = 0; row < 8; row++)
{ {
Vector512<long> lower = Vector512.LoadUnsafe(ref scratchBase, (nuint)(row * 8)); Vector512<long> lower = Vector512.LoadUnsafe(ref scratchBase, (nuint)(row * 8));
@ -996,7 +996,7 @@ internal static class Av1Transform2dOperations
} }
/// <summary> /// <summary>
/// Applies the terminal operations which libaom performs before transposing a signed sixteen-bit tile. /// Applies the terminal operations which the reference decoder performs before transposing a signed sixteen-bit tile.
/// </summary> /// </summary>
/// <param name="value">The packed transform values.</param> /// <param name="value">The packed transform values.</param>
/// <param name="roundShift">The signed AV1 pipeline shift.</param> /// <param name="roundShift">The signed AV1 pipeline shift.</param>
@ -1015,7 +1015,7 @@ internal static class Av1Transform2dOperations
} }
/// <summary> /// <summary>
/// Applies the terminal operations which libaom performs before transposing four signed thirty-two-bit lanes. /// Applies the terminal operations which the reference decoder performs before transposing four signed thirty-two-bit lanes.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<int> Finish(Vector128<int> value, int roundShift, bool normalizeRectangle) private static Vector128<int> Finish(Vector128<int> value, int roundShift, bool normalizeRectangle)
@ -1027,7 +1027,7 @@ internal static class Av1Transform2dOperations
} }
/// <summary> /// <summary>
/// Applies the terminal operations which libaom performs before transposing eight signed thirty-two-bit lanes. /// Applies the terminal operations which the reference decoder performs before transposing eight signed thirty-two-bit lanes.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<int> Finish(Vector256<int> value, int roundShift, bool normalizeRectangle) private static Vector256<int> Finish(Vector256<int> value, int roundShift, bool normalizeRectangle)

2
src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformWorkspace.cs

@ -21,7 +21,7 @@ internal static class Av1TransformWorkspace
public const int Vector512StorageLength = 3 * Av1Constants.MaxTransformSize * 16; public const int Vector512StorageLength = 3 * Av1Constants.MaxTransformSize * 16;
/// <summary> /// <summary>
/// The number of integer elements occupied by the libaom-shaped AVX-512 transpose scratch. /// The number of integer elements occupied by the AVX-512 transpose scratch.
/// </summary> /// </summary>
public const int Vector512TransposeStorageLength = 16 * 8 * 2; public const int Vector512TransposeStorageLength = 16 * 8 * 2;

6
src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Inverse2dTransformer.Adst4Operator.cs

@ -28,7 +28,7 @@ internal static partial class Av1Inverse2dTransformer
{ {
ReadOnlySpan<int> sinpi = Av1SinusConstants.SinusPi(cosBit); ReadOnlySpan<int> sinpi = Av1SinusConstants.SinusPi(cosBit);
// libaom widens the complete four-point factorization because the products retain their fixed-point scale // the reference decoder widens the complete four-point factorization because the products retain their fixed-point scale
// until the final shift. The stage buffer is therefore unnecessary for this transform size. // until the final shift. The stage buffer is therefore unnecessary for this transform size.
long x0 = input[0]; long x0 = input[0];
long x1 = input[1]; long x1 = input[1];
@ -38,7 +38,7 @@ internal static partial class Av1Inverse2dTransformer
_ = step; _ = step;
_ = stageRange; _ = stageRange;
// Avoid the multiplications for the all-zero coefficient vector, matching libaom's scalar kernel. // Avoid the multiplications for the all-zero coefficient vector, matching the reference decoder's scalar kernel.
if ((x0 | x1 | x2 | x3) == 0) if ((x0 | x1 | x2 | x3) == 0)
{ {
output[..4].Clear(); output[..4].Clear();
@ -89,7 +89,7 @@ internal static partial class Av1Inverse2dTransformer
Vector128<int> x2 = input.V2; Vector128<int> x2 = input.V2;
Vector128<int> x3 = input.V3; Vector128<int> x3 = input.V3;
// Pinned libaom retains the sine-table scale in Int32 products and sums, but performs the twelve-bit row // The reference decoder retains the sine-table scale in Int32 products and sums, but performs the twelve-bit row
// kernel's terminal scaling and rounding in Int64. This is the only stage whose rounding bias can overflow // kernel's terminal scaling and rounding in Int64. This is the only stage whose rounding bias can overflow
// a valid Int32 fixed-point sum. // a valid Int32 fixed-point sum.
if (widenedRound) if (widenedRound)

2
src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Inverse2dTransformer.Identity16Operator.cs

@ -46,7 +46,7 @@ internal static partial class Av1Inverse2dTransformer
Av1TransformStageRange stageRange) Av1TransformStageRange stageRange)
{ {
// The doubled scale exceeds Int32 only for the 20-bit twelve-bit row range. Widen that exact product and // The doubled scale exceeds Int32 only for the 20-bit twelve-bit row range. Widen that exact product and
// rounding sequence, matching libaom without changing the established lower-range SIMD path. // rounding sequence, matching the reference decoder without changing the established lower-range SIMD path.
if (stageRange[0] >= Av1Transform1dMath.WidenedIntermediateBitCount) if (stageRange[0] >= Av1Transform1dMath.WidenedIntermediateBitCount)
{ {
Av1IdentityTransform1d.TransformWidened(ref input, ref output, 16, 2 * Av1Transform1dMath.NewSqrt2, Av1Transform1dMath.NewSqrt2Bits); Av1IdentityTransform1d.TransformWidened(ref input, ref output, 16, 2 * Av1Transform1dMath.NewSqrt2, Av1Transform1dMath.NewSqrt2Bits);

2
src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Inverse2dTransformer.Identity4Operator.cs

@ -46,7 +46,7 @@ internal static partial class Av1Inverse2dTransformer
Av1TransformStageRange stageRange) Av1TransformStageRange stageRange)
{ {
// Only a twelve-bit row transform has the 20-bit input range that can overflow this fixed-point product. // Only a twelve-bit row transform has the 20-bit input range that can overflow this fixed-point product.
// Match libaom's high-bit-depth kernel there while retaining the compact Int32 path for narrower ranges. // Match the reference decoder's high-bit-depth kernel there while retaining the compact Int32 path for narrower ranges.
if (stageRange[0] >= Av1Transform1dMath.WidenedIntermediateBitCount) if (stageRange[0] >= Av1Transform1dMath.WidenedIntermediateBitCount)
{ {
Av1IdentityTransform1d.TransformWidened(ref input, ref output, 4, Av1Transform1dMath.NewSqrt2, Av1Transform1dMath.NewSqrt2Bits); Av1IdentityTransform1d.TransformWidened(ref input, ref output, 4, Av1Transform1dMath.NewSqrt2, Av1Transform1dMath.NewSqrt2Bits);

6
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CdefFilterTests.cs

@ -32,21 +32,21 @@ public class Av1CdefFilterTests
/// Verifies direction selection and variance against an independent scalar definition. /// Verifies direction selection and variance against an independent scalar definition.
/// </summary> /// </summary>
[Fact] [Fact]
public void FindDirectionMatchesIndependentDefinitionAcrossIntrinsicTiers() public void FindDirectionMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateDirections, Configurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateDirections, Configurations);
/// <summary> /// <summary>
/// Verifies every CDEF block geometry and strength mode against an independent scalar definition. /// Verifies every CDEF block geometry and strength mode against an independent scalar definition.
/// </summary> /// </summary>
[Fact] [Fact]
public void FilterBlockMatchesIndependentDefinitionAcrossIntrinsicTiers() public void FilterBlockMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateFilters, Configurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateFilters, Configurations);
/// <summary> /// <summary>
/// Verifies eight-bit widening and 16-bit copying across packed and scalar execution tiers. /// Verifies eight-bit widening and 16-bit copying across packed and scalar execution tiers.
/// </summary> /// </summary>
[Fact] [Fact]
public void CopyPlaneMatchesIndependentDefinitionAcrossIntrinsicTiers() public void CopyPlaneMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePlaneCopies, Configurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePlaneCopies, Configurations);
/// <summary> /// <summary>

6
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ChromaFromLumaTests.cs

@ -31,7 +31,7 @@ public class Av1ChromaFromLumaTests
[InlineData(false, false, new short[] { 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128 })] [InlineData(false, false, new short[] { 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128 })]
[InlineData(true, false, new short[] { 12, 28, 44, 60, 76, 92, 108, 124 })] [InlineData(true, false, new short[] { 12, 28, 44, 60, 76, 92, 108, 124 })]
[InlineData(true, true, new short[] { 28, 44, 92, 108 })] [InlineData(true, true, new short[] { 28, 44, 92, 108 })]
public void Store8BitMatchesLibaomSubsampling(bool subX, bool subY, short[] expected) public void Store8BitMatchesReference(bool subX, bool subY, short[] expected)
{ {
ObuColorConfig colorConfig = new() { SubSamplingX = subX, SubSamplingY = subY }; ObuColorConfig colorConfig = new() { SubSamplingX = subX, SubSamplingY = subY };
Av1ChromaFromLumaContext context = new(colorConfig); Av1ChromaFromLumaContext context = new(colorConfig);
@ -144,14 +144,14 @@ public class Av1ChromaFromLumaTests
/// Verifies exact 8-, 10-, and 12-bit CfL output and padding preservation across all intrinsic tiers. /// Verifies exact 8-, 10-, and 12-bit CfL output and padding preservation across all intrinsic tiers.
/// </summary> /// </summary>
[Fact] [Fact]
public void PredictMatchesIndependentDefinitionAcrossIntrinsicWidths() public void PredictMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePredictors, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePredictors, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies exact luma subsampling and average subtraction across all intrinsic tiers. /// Verifies exact luma subsampling and average subtraction across all intrinsic tiers.
/// </summary> /// </summary>
[Fact] [Fact]
public void ContextOperationsMatchIndependentDefinitionAcrossIntrinsicWidths() public void ContextOperationsMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateContextOperations, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateContextOperations, PredictorConfigurations);
/// <summary> /// <summary>

26
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs

@ -40,7 +40,7 @@ public class Av1CompoundBlockDecoderTests
[InlineData((int)Av1BitDepth.EightBit)] [InlineData((int)Av1BitDepth.EightBit)]
[InlineData((int)Av1BitDepth.TenBit)] [InlineData((int)Av1BitDepth.TenBit)]
[InlineData((int)Av1BitDepth.TwelveBit)] [InlineData((int)Av1BitDepth.TwelveBit)]
public void DecodeBlockReconstructsEqualAverageCompoundPrediction(int bitDepthValue) public void DecodeBlockWithAverageCompound(int bitDepthValue)
{ {
Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue; Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue;
ushort firstValue = bitDepth == Av1BitDepth.EightBit ? (ushort)20 : (ushort)100; ushort firstValue = bitDepth == Av1BitDepth.EightBit ? (ushort)20 : (ushort)100;
@ -127,7 +127,7 @@ public class Av1CompoundBlockDecoderTests
/// Verifies that high-bit-depth subpixel predictors retain their no-round precision until the compound average. /// Verifies that high-bit-depth subpixel predictors retain their no-round precision until the compound average.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeBlockReconstructsSubpixelHighBitDepthEqualAverageCompoundPrediction() public void DecodeBlockWithHighBitDepthAverageCompound()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateSubpixelHighBitDepthEqualAverageCompoundPrediction, ValidateSubpixelHighBitDepthEqualAverageCompoundPrediction,
CompoundPredictionConfigurations); CompoundPredictionConfigurations);
@ -136,7 +136,7 @@ public class Av1CompoundBlockDecoderTests
/// Verifies that high-bit-depth subpixel predictors retain no-round precision until distance weighting. /// Verifies that high-bit-depth subpixel predictors retain no-round precision until distance weighting.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeBlockReconstructsSubpixelHighBitDepthDistanceWeightedCompoundPrediction() public void DecodeBlockWithHighBitDepthDistanceWeightedCompound()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateSubpixelHighBitDepthDistanceWeightedCompoundPrediction, ValidateSubpixelHighBitDepthDistanceWeightedCompoundPrediction,
CompoundPredictionConfigurations); CompoundPredictionConfigurations);
@ -145,7 +145,7 @@ public class Av1CompoundBlockDecoderTests
/// Verifies that high-bit-depth subpixel predictors retain no-round precision until wedge blending. /// Verifies that high-bit-depth subpixel predictors retain no-round precision until wedge blending.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeBlockReconstructsSubpixelHighBitDepthWedgeCompoundPrediction() public void DecodeBlockWithHighBitDepthWedgeCompound()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateSubpixelHighBitDepthWedgeCompoundPrediction, ValidateSubpixelHighBitDepthWedgeCompoundPrediction,
CompoundPredictionConfigurations); CompoundPredictionConfigurations);
@ -154,7 +154,7 @@ public class Av1CompoundBlockDecoderTests
/// Verifies that high-bit-depth subpixel predictors retain no-round precision through difference masking and blending. /// Verifies that high-bit-depth subpixel predictors retain no-round precision through difference masking and blending.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeBlockReconstructsSubpixelHighBitDepthDifferenceWeightedCompoundPrediction() public void DecodeBlockWithHighBitDepthDifferenceWeightedCompound()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateSubpixelHighBitDepthDifferenceWeightedCompoundPrediction, ValidateSubpixelHighBitDepthDifferenceWeightedCompoundPrediction,
CompoundPredictionConfigurations); CompoundPredictionConfigurations);
@ -163,7 +163,7 @@ public class Av1CompoundBlockDecoderTests
/// Verifies that both references of a GLOBAL_GLOBALMV block use their complete matrix before compound averaging. /// Verifies that both references of a GLOBAL_GLOBALMV block use their complete matrix before compound averaging.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeBlockReconstructsCompoundGlobalWarpPrediction() public void DecodeBlockWithCompoundGlobalWarp()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateCompoundGlobalWarpPrediction, ValidateCompoundGlobalWarpPrediction,
GlobalWarpConfigurations); GlobalWarpConfigurations);
@ -176,7 +176,7 @@ public class Av1CompoundBlockDecoderTests
[InlineData((int)Av1BitDepth.EightBit)] [InlineData((int)Av1BitDepth.EightBit)]
[InlineData((int)Av1BitDepth.TenBit)] [InlineData((int)Av1BitDepth.TenBit)]
[InlineData((int)Av1BitDepth.TwelveBit)] [InlineData((int)Av1BitDepth.TwelveBit)]
public void DecodeBlockReconstructsScaledSingleReferencePrediction(int bitDepthValue) public void DecodeBlockWithScaledReference(int bitDepthValue)
{ {
Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue; Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue;
ObuSequenceHeader sequenceHeader = CreateSequenceHeader(bitDepth, 16); ObuSequenceHeader sequenceHeader = CreateSequenceHeader(bitDepth, 16);
@ -243,7 +243,7 @@ public class Av1CompoundBlockDecoderTests
/// Verifies that scaled predictors retain their no-round precision until compound averaging. /// Verifies that scaled predictors retain their no-round precision until compound averaging.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeBlockReconstructsScaledCompoundPrediction() public void DecodeBlockWithScaledCompound()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateScaledCompoundPrediction, ValidateScaledCompoundPrediction,
CompoundPredictionConfigurations); CompoundPredictionConfigurations);
@ -263,7 +263,7 @@ public class Av1CompoundBlockDecoderTests
[InlineData((int)Av1BitDepth.EightBit, (int)Av1CompoundType.DifferenceWeighted)] [InlineData((int)Av1BitDepth.EightBit, (int)Av1CompoundType.DifferenceWeighted)]
[InlineData((int)Av1BitDepth.TenBit, (int)Av1CompoundType.DifferenceWeighted)] [InlineData((int)Av1BitDepth.TenBit, (int)Av1CompoundType.DifferenceWeighted)]
[InlineData((int)Av1BitDepth.TwelveBit, (int)Av1CompoundType.DifferenceWeighted)] [InlineData((int)Av1BitDepth.TwelveBit, (int)Av1CompoundType.DifferenceWeighted)]
public void DecodeBlockReconstructsSelectableCompoundPrediction(int bitDepthValue, int compoundTypeValue) public void DecodeBlockWithSelectableCompound(int bitDepthValue, int compoundTypeValue)
{ {
Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue; Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue;
Av1CompoundType compoundType = (Av1CompoundType)compoundTypeValue; Av1CompoundType compoundType = (Av1CompoundType)compoundTypeValue;
@ -381,7 +381,7 @@ public class Av1CompoundBlockDecoderTests
[InlineData((int)Av1BitDepth.EightBit)] [InlineData((int)Av1BitDepth.EightBit)]
[InlineData((int)Av1BitDepth.TenBit)] [InlineData((int)Av1BitDepth.TenBit)]
[InlineData((int)Av1BitDepth.TwelveBit)] [InlineData((int)Av1BitDepth.TwelveBit)]
public void DecodeBlockReconstructsSmoothInterIntraPrediction(int bitDepthValue) public void DecodeBlockWithSmoothInterIntra(int bitDepthValue)
{ {
Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue; Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue;
ushort interValue = bitDepth == Av1BitDepth.EightBit ? (ushort)20 : (ushort)100; ushort interValue = bitDepth == Av1BitDepth.EightBit ? (ushort)20 : (ushort)100;
@ -464,7 +464,7 @@ public class Av1CompoundBlockDecoderTests
[InlineData((int)Av1BitDepth.EightBit)] [InlineData((int)Av1BitDepth.EightBit)]
[InlineData((int)Av1BitDepth.TenBit)] [InlineData((int)Av1BitDepth.TenBit)]
[InlineData((int)Av1BitDepth.TwelveBit)] [InlineData((int)Av1BitDepth.TwelveBit)]
public void DecodeBlockReconstructsOverlappedMotionCompensation(int bitDepthValue) public void DecodeBlockWithObmc(int bitDepthValue)
{ {
const int frameSize = 24; const int frameSize = 24;
const int blockOrigin = 8; const int blockOrigin = 8;
@ -556,7 +556,7 @@ public class Av1CompoundBlockDecoderTests
[Theory] [Theory]
[InlineData((int)Av1ColorFormat.Yuv420)] [InlineData((int)Av1ColorFormat.Yuv420)]
[InlineData((int)Av1ColorFormat.Yuv422)] [InlineData((int)Av1ColorFormat.Yuv422)]
public void DecodeBlockReconstructsSubsampledOverlappedMotionCompensation(int colorFormatValue) public void DecodeBlockWithSubsampledObmc(int colorFormatValue)
{ {
const int frameSize = 48; const int frameSize = 48;
const int blockOrigin = 16; const int blockOrigin = 16;
@ -1475,7 +1475,7 @@ public class Av1CompoundBlockDecoderTests
{ {
int predictionIndex = (row * blockSize) + column; int predictionIndex = (row * blockSize) + column;
// Libaom truncates the equal average before removing the compound bias, then performs the sole final // The reference decoder truncates the equal average before removing the compound bias, then performs the sole final
// rounding step. Reconstructing each reference to native pixels first can differ from this result. // rounding step. Reconstructing each reference to native pixels first can differ from this result.
int intermediate = ((firstHighBitDepthPrediction[predictionIndex] + int intermediate = ((firstHighBitDepthPrediction[predictionIndex] +
secondHighBitDepthPrediction[predictionIndex]) >> 1) - compoundRoundOffset; secondHighBitDepthPrediction[predictionIndex]) >> 1) - compoundRoundOffset;

26
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundInterPredictorTests.cs

@ -24,27 +24,27 @@ public class Av1CompoundInterPredictorTests
/// Verifies rounded 8-bit averaging, scalar tails, and untouched row padding under every SIMD configuration. /// Verifies rounded 8-bit averaging, scalar tails, and untouched row padding under every SIMD configuration.
/// </summary> /// </summary>
[Fact] [Fact]
public void ByteAverageMatchesIndependentOracleAcrossIntrinsicWidths() public void ByteAverageMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateByteAverage, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateByteAverage, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies rounded 10/12-bit averaging, scalar tails, and untouched row padding under every SIMD configuration. /// Verifies rounded 10/12-bit averaging, scalar tails, and untouched row padding under every SIMD configuration.
/// </summary> /// </summary>
[Fact] [Fact]
public void HighBitDepthAverageMatchesIndependentOracleAcrossIntrinsicWidths() public void HighBitDepthAverageMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthAverage, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthAverage, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies 10/12-bit no-round prediction and compound finalization across every intrinsic width. /// Verifies 10/12-bit no-round prediction and compound finalization across every intrinsic width.
/// </summary> /// </summary>
[Fact] [Fact]
public void HighBitDepthCompoundIntermediatesMatchIndependentOracleAcrossIntrinsicWidths() public void HighBitDepthIntermediatesMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateHighBitDepthCompoundIntermediates, ValidateHighBitDepthCompoundIntermediates,
PredictorConfigurations); PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies every current libaom display-distance quantization class in both temporal directions. /// Verifies every display-distance quantization class in both temporal directions.
/// </summary> /// </summary>
/// <param name="firstOrderHint">The first reference order hint.</param> /// <param name="firstOrderHint">The first reference order hint.</param>
/// <param name="secondOrderHint">The second reference order hint.</param> /// <param name="secondOrderHint">The second reference order hint.</param>
@ -61,7 +61,7 @@ public class Av1CompoundInterPredictorTests
[InlineData(12, 17, 3, 13)] [InlineData(12, 17, 3, 13)]
[InlineData(12, 16, 3, 13)] [InlineData(12, 16, 3, 13)]
[InlineData(16, 20, 13, 3)] [InlineData(16, 20, 13, 3)]
public void DistanceWeightsMatchCurrentLibaomQuantization( public void DistanceWeightsMatchReference(
int firstOrderHint, int firstOrderHint,
int secondOrderHint, int secondOrderHint,
int expectedFirstWeight, int expectedFirstWeight,
@ -95,21 +95,21 @@ public class Av1CompoundInterPredictorTests
/// Verifies 8-bit distance and per-sample mask blending across every intrinsic width and scalar tail. /// Verifies 8-bit distance and per-sample mask blending across every intrinsic width and scalar tail.
/// </summary> /// </summary>
[Fact] [Fact]
public void ByteSelectableBlendsMatchIndependentOracleAcrossIntrinsicWidths() public void ByteSelectableBlendsMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateByteSelectableBlends, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateByteSelectableBlends, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies 10/12-bit distance and per-sample mask blending across every intrinsic width and scalar tail. /// Verifies 10/12-bit distance and per-sample mask blending across every intrinsic width and scalar tail.
/// </summary> /// </summary>
[Fact] [Fact]
public void HighBitDepthSelectableBlendsMatchIndependentOracleAcrossIntrinsicWidths() public void HighBitDepthSelectableBlendsMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthSelectableBlends, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthSelectableBlends, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies the four smooth inter-intra modes and their complemented destination orientation. /// Verifies the four smooth inter-intra modes and their complemented destination orientation.
/// </summary> /// </summary>
[Fact] [Fact]
public void SmoothInterIntraMasksMatchCurrentLibaomWeights() public void SmoothInterIntraMasksMatchReference()
{ {
ReadOnlySpan<byte> weights = [60, 34, 19, 11, 6, 4, 2, 1]; ReadOnlySpan<byte> weights = [60, 34, 19, 11, 6, 4, 2, 1];
@ -152,10 +152,10 @@ public class Av1CompoundInterPredictorTests
} }
/// <summary> /// <summary>
/// Verifies the current libaom horizontal curve at the index exercised by a 32-by-16 inter-intra block. /// Verifies the reference horizontal curve at the index exercised by a 32-by-16 inter-intra block.
/// </summary> /// </summary>
[Fact] [Fact]
public void HorizontalInterIntraMaskMatchesCurrentLibaomThirtyTwoWideCurve() public void HorizontalInterIntraMaskMatchesReference()
{ {
const int width = 32; const int width = 32;
const int height = 16; const int height = 16;
@ -170,14 +170,14 @@ public class Av1CompoundInterPredictorTests
} }
/// <summary> /// <summary>
/// Verifies the current libaom difference-mask formula in both orientations at each supported bit depth. /// Verifies the reference difference-mask formula in both orientations at each supported bit depth.
/// </summary> /// </summary>
[Fact] [Fact]
public void DifferenceWeightedMasksMatchCurrentLibaomFormula() public void DifferenceWeightedMasksMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateDifferenceWeightedMasks, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateDifferenceWeightedMasks, PredictorConfigurations);
/// <summary> /// <summary>
/// Applies the current libaom difference-mask formula at every bit depth and intrinsic width. /// Applies the independent difference-mask formula at every bit depth and intrinsic width.
/// </summary> /// </summary>
private static void ValidateDifferenceWeightedMasks() private static void ValidateDifferenceWeightedMasks()
{ {

12
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundReferenceEntropyTests.cs

@ -10,16 +10,16 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
/// <summary> /// <summary>
/// Verifies AV1 compound-reference selection and compound inter-mode entropy against current official libaom main. /// Verifies AV1 compound-reference selection and compound inter-mode entropy against the current AV1 reference.
/// </summary> /// </summary>
[Trait("Format", "Avif")] [Trait("Format", "Avif")]
public class Av1CompoundReferenceEntropyTests public class Av1CompoundReferenceEntropyTests
{ {
/// <summary> /// <summary>
/// Verifies every binary compound-reference default against libaom's forward Q15 tables. /// Verifies every binary compound-reference default against the reference decoder's forward Q15 tables.
/// </summary> /// </summary>
[Fact] [Fact]
public void CompoundReferenceDefaultsMatchLibaom() public void CompoundReferenceDefaultsMatchReference()
{ {
AssertBinaryDefaults( AssertBinaryDefaults(
[1198, 2070, 9166, 7499, 22475], [1198, 2070, 9166, 7499, 22475],
@ -51,10 +51,10 @@ public class Av1CompoundReferenceEntropyTests
} }
/// <summary> /// <summary>
/// Verifies all eight compound inter-mode defaults against libaom's forward Q15 tables. /// Verifies all eight compound inter-mode defaults against the reference decoder's forward Q15 tables.
/// </summary> /// </summary>
[Fact] [Fact]
public void InterCompoundModeDefaultsMatchLibaom() public void InterCompoundModeDefaultsMatchReference()
{ {
uint[][] expected = uint[][] expected =
[ [
@ -151,7 +151,7 @@ public class Av1CompoundReferenceEntropyTests
/// Verifies the compound-reference type context across intra, single, bidirectional, and unidirectional neighbors. /// Verifies the compound-reference type context across intra, single, bidirectional, and unidirectional neighbors.
/// </summary> /// </summary>
[Fact] [Fact]
public void CompoundReferenceTypeContextMatchesLibaom() public void CompoundReferenceTypeContextMatchesReference()
{ {
Av1BlockModeInfo intra = CreateModeInfo(Av1ReferenceFrameType.Intra, Av1ReferenceFrameType.None); Av1BlockModeInfo intra = CreateModeInfo(Av1ReferenceFrameType.Intra, Av1ReferenceFrameType.None);
Av1BlockModeInfo singleForward = CreateModeInfo(Av1ReferenceFrameType.Last, Av1ReferenceFrameType.None); Av1BlockModeInfo singleForward = CreateModeInfo(Av1ReferenceFrameType.Last, Av1ReferenceFrameType.None);

2
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs

@ -54,7 +54,7 @@ public class Av1DeblockingFilterTests
/// Verifies skipped inter-edge decisions and reference and mode level deltas through the production frame filter. /// Verifies skipped inter-edge decisions and reference and mode level deltas through the production frame filter.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeFrameMatchesCurrentLibaomInterEdgeAndDeltaDecisions() public void DecodeFrameMatchesReference()
{ {
ValidateInterEdgeAndDeltaDecisions( ValidateInterEdgeAndDeltaDecisions(
Av1PredictionMode.GlobalMotionVector, Av1PredictionMode.GlobalMotionVector,

6
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs

@ -58,7 +58,7 @@ public class Av1ForwardTransformTests
/// Verifies every permitted size, type, and bit-depth combination against the direct scalar two-axis definition. /// Verifies every permitted size, type, and bit-depth combination against the direct scalar two-axis definition.
/// </summary> /// </summary>
[Fact] [Fact]
public void TwoDimensionalPipelineMatchesScalarReferenceAcrossHardwareConfigurations() public void TwoDimensionalPipelineMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertTwoDimensionalPipeline, TransformConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertTwoDimensionalPipeline, TransformConfigurations);
/// <summary> /// <summary>
@ -138,7 +138,7 @@ public class Av1ForwardTransformTests
} }
/// <summary> /// <summary>
/// Compares one integer stage network with the analytical transform used by the libaom forward-transform tests. /// Compares one integer stage network with the analytical reference transform.
/// </summary> /// </summary>
/// <typeparam name="TOperator">The transform operator.</typeparam> /// <typeparam name="TOperator">The transform operator.</typeparam>
/// <param name="transformType">The analytical transform definition.</param> /// <param name="transformType">The analytical transform definition.</param>
@ -170,7 +170,7 @@ public class Av1ForwardTransformTests
TOperator.Transform(ref valuesBase, sizeof(int), sizeof(int), ref buffer0, ref buffer1, cosBit); TOperator.Transform(ref valuesBase, sizeof(int), sizeof(int), ref buffer0, ref buffer1, cosBit);
Av1ReferenceTransform.ReferenceTransform1d(transformType, referenceInput, referenceOutput, length); Av1ReferenceTransform.ReferenceTransform1d(transformType, referenceInput, referenceOutput, length);
// libaom permits seven integer coefficient units because each fixed-point butterfly rounds independently. // the reference decoder permits seven integer coefficient units because each fixed-point butterfly rounds independently.
for (int index = 0; index < length; index++) for (int index = 0; index < length; index++)
{ {
int expected = (int)Math.Round(referenceOutput[index], MidpointRounding.AwayFromZero); int expected = (int)Math.Round(referenceOutput[index], MidpointRounding.AwayFromZero);

4
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1GlobalMotionParametersTests.cs

@ -84,10 +84,10 @@ public class Av1GlobalMotionParametersTests
} }
/// <summary> /// <summary>
/// Verifies local least-squares projection against a multi-sample model traced from current libaom. /// Verifies local least-squares projection against a multi-sample model traced from the reference decoder.
/// </summary> /// </summary>
[Fact] [Fact]
public void LocalProjectionMatchesCurrentLibaomMultiSampleModel() public void LocalProjectionMatchesReference()
{ {
Point[] sourcePoints = [new(24, -40), new(-40, 24), new(-24, -24), new(72, -24)]; Point[] sourcePoints = [new(24, -40), new(-40, 24), new(-24, -24), new(72, -24)];
Point[] referencePoints = [new(-16, -8), new(-72, 64), new(-64, 16), new(32, 8)]; Point[] referencePoints = [new(-16, -8), new(-72, 64), new(-64, 16), new(32, 8)];

14
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterFrameIntraEntropyTests.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
public class Av1InterFrameIntraEntropyTests public class Av1InterFrameIntraEntropyTests
{ {
/// <summary> /// <summary>
/// Gets libaom's four forward Q15 luma-mode CDF rows in block-size-group order. /// Gets the reference decoder's four forward Q15 luma-mode CDF rows in block-size-group order.
/// </summary> /// </summary>
private static ReadOnlySpan<ushort> FrameYModeForwardThresholds => private static ReadOnlySpan<ushort> FrameYModeForwardThresholds =>
[ [
@ -28,10 +28,10 @@ public class Av1InterFrameIntraEntropyTests
]; ];
/// <summary> /// <summary>
/// Verifies the four normative intra/inter distributions against libaom's forward Q15 defaults. /// Verifies the four normative intra/inter distributions against the reference decoder's forward Q15 defaults.
/// </summary> /// </summary>
[Fact] [Fact]
public void IntraInterDefaultsMatchLibaom() public void IntraInterDefaultsMatchReference()
{ {
uint[] forwardThresholds = [806, 16662, 20186, 26538]; uint[] forwardThresholds = [806, 16662, 20186, 26538];
Av1Distribution[] distributions = Av1DefaultDistributions.IntraInter; Av1Distribution[] distributions = Av1DefaultDistributions.IntraInter;
@ -39,7 +39,7 @@ public class Av1InterFrameIntraEntropyTests
Assert.Equal(forwardThresholds.Length, distributions.Length); Assert.Equal(forwardThresholds.Length, distributions.Length);
for (int context = 0; context < distributions.Length; context++) for (int context = 0; context < distributions.Length; context++)
{ {
// Av1Distribution stores inverse cumulative thresholds, so compare each libaom default after the same // Av1Distribution stores inverse cumulative thresholds, so compare each forward default after the same
// forward-to-inverse conversion performed by its constructor. // forward-to-inverse conversion performed by its constructor.
Assert.Equal((uint)Av1Distribution.ProbabilityTop - forwardThresholds[context], distributions[context][0]); Assert.Equal((uint)Av1Distribution.ProbabilityTop - forwardThresholds[context], distributions[context][0]);
Assert.Equal(2, distributions[context].NumberOfSymbols); Assert.Equal(2, distributions[context].NumberOfSymbols);
@ -47,10 +47,10 @@ public class Av1InterFrameIntraEntropyTests
} }
/// <summary> /// <summary>
/// Verifies every inter-frame intra luma-mode threshold against libaom's forward Q15 defaults. /// Verifies every inter-frame intra luma-mode threshold against the reference decoder's forward Q15 defaults.
/// </summary> /// </summary>
[Fact] [Fact]
public void FrameYModeDefaultsMatchLibaom() public void FrameYModeDefaultsMatchReference()
{ {
const int thresholdsPerGroup = 12; const int thresholdsPerGroup = 12;
ReadOnlySpan<ushort> forwardThresholds = FrameYModeForwardThresholds; ReadOnlySpan<ushort> forwardThresholds = FrameYModeForwardThresholds;
@ -229,7 +229,7 @@ public class Av1InterFrameIntraEntropyTests
/// <returns>Every decoded block size paired with its luma-mode size group.</returns> /// <returns>Every decoded block size paired with its luma-mode size group.</returns>
public static TheoryData<int, int> GetBlockSizeGroups() public static TheoryData<int, int> GetBlockSizeGroups()
{ {
// This is size_group_lookup from AV1 section 9.3 and libaom common_data.h. Keeping expected values explicit // This is size_group_lookup from AV1 section 9.3 and the normative lookup table. Keeping expected values explicit
// ensures that the test does not reproduce the production formula it is intended to verify. // ensures that the test does not reproduce the production formula it is intended to verify.
int[] sizeGroups = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 0, 0, 1, 1, 2, 2]; int[] sizeGroups = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 0, 0, 1, 1, 2, 2];
TheoryData<int, int> result = []; TheoryData<int, int> result = [];

10
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterFrameModeInfoTests.cs

@ -57,7 +57,7 @@ public class Av1InterFrameModeInfoTests
/// Verifies that skip mode omits the residual-skip and intra-inter symbols and marks the block as inter coded. /// Verifies that skip mode omits the residual-skip and intra-inter symbols and marks the block as inter coded.
/// </summary> /// </summary>
[Fact] [Fact]
public void ReadInterFrameModeInfoSkipModeForcesInterBlockAndResidualSkip() public void SkipModeForcesInterAndSkipsResidual()
{ {
ObuSequenceHeader sequenceHeader = CreateSequenceHeader(); ObuSequenceHeader sequenceHeader = CreateSequenceHeader();
sequenceHeader.OrderHintInfo.EnableOrderHint = true; sequenceHeader.OrderHintInfo.EnableOrderHint = true;
@ -150,7 +150,7 @@ public class Av1InterFrameModeInfoTests
/// Verifies that an identity global-motion block omits switchable interpolation-filter symbols. /// Verifies that an identity global-motion block omits switchable interpolation-filter symbols.
/// </summary> /// </summary>
[Fact] [Fact]
public void ReadInterFrameModeInfoOmitsInterpolationFiltersForIdentityGlobalMotion() public void IdentityGlobalMotionOmitsInterpolationFilters()
{ {
ObuSequenceHeader sequenceHeader = CreateSequenceHeader(); ObuSequenceHeader sequenceHeader = CreateSequenceHeader();
sequenceHeader.EnableDualFilter = true; sequenceHeader.EnableDualFilter = true;
@ -167,8 +167,8 @@ public class Av1InterFrameModeInfoTests
using Av1SymbolWriter writer = new(Configuration.Default, 3, updateCdf: true); using Av1SymbolWriter writer = new(Configuration.Default, 3, updateCdf: true);
writer.WriteSymbol(false, Av1DefaultDistributions.Skip[0]); writer.WriteSymbol(false, Av1DefaultDistributions.Skip[0]);
// Current libaom's is_nontrans_global_motion rejects only TRANSLATION, so the default identity model omits // Identity is distinct from Translation for this syntax gate. These sentinel symbols must remain unread even
// these sentinel symbols even though is_global_mv_block uses the separate greater-than-translation classification. // though the separate global-motion-block classification requires a model greater than Translation.
writer.WriteSymbol((int)Av1InterpolationFilter.Smooth, Av1DefaultDistributions.SwitchableInterpolation[3]); writer.WriteSymbol((int)Av1InterpolationFilter.Smooth, Av1DefaultDistributions.SwitchableInterpolation[3]);
writer.WriteSymbol((int)Av1InterpolationFilter.Sharp, Av1DefaultDistributions.SwitchableInterpolation[11]); writer.WriteSymbol((int)Av1InterpolationFilter.Sharp, Av1DefaultDistributions.SwitchableInterpolation[11]);
using IMemoryOwner<byte> encoded = writer.Exit(); using IMemoryOwner<byte> encoded = writer.Exit();
@ -234,7 +234,7 @@ public class Av1InterFrameModeInfoTests
[InlineData((int)Av1CompoundType.DistanceWeighted)] [InlineData((int)Av1CompoundType.DistanceWeighted)]
[InlineData((int)Av1CompoundType.Wedge)] [InlineData((int)Av1CompoundType.Wedge)]
[InlineData((int)Av1CompoundType.DifferenceWeighted)] [InlineData((int)Av1CompoundType.DifferenceWeighted)]
public void ReadInterFrameModeInfoReadsSelectableCompoundBeforeInterpolation(int compoundTypeValue) public void ReadsSelectableCompoundBeforeInterpolation(int compoundTypeValue)
{ {
Av1CompoundType compoundType = (Av1CompoundType)compoundTypeValue; Av1CompoundType compoundType = (Av1CompoundType)compoundTypeValue;
ObuSequenceHeader sequenceHeader = CreateSequenceHeader(); ObuSequenceHeader sequenceHeader = CreateSequenceHeader();

8
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterIntraEntropyTests.cs

@ -15,10 +15,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
public class Av1InterIntraEntropyTests public class Av1InterIntraEntropyTests
{ {
/// <summary> /// <summary>
/// Verifies the four block-size-group distributions against libaom's forward Q15 defaults. /// Verifies the four block-size-group distributions against the reference decoder's forward Q15 defaults.
/// </summary> /// </summary>
[Fact] [Fact]
public void DefaultsMatchLibaom() public void DefaultsMatchReference()
{ {
ReadOnlySpan<uint> forwardThresholds = [16384, 26887, 27597, 30237]; ReadOnlySpan<uint> forwardThresholds = [16384, 26887, 27597, 30237];
Av1Distribution[] distributions = Av1DefaultDistributions.InterIntra; Av1Distribution[] distributions = Av1DefaultDistributions.InterIntra;
@ -26,7 +26,7 @@ public class Av1InterIntraEntropyTests
Assert.Equal(forwardThresholds.Length, distributions.Length); Assert.Equal(forwardThresholds.Length, distributions.Length);
for (int group = 0; group < distributions.Length; group++) for (int group = 0; group < distributions.Length; group++)
{ {
// Av1Distribution stores inverse cumulative thresholds, so convert libaom's forward threshold before // Av1Distribution stores inverse cumulative thresholds, so convert the reference decoder's forward threshold before
// comparing the exact Q15 state consumed by the range decoder. // comparing the exact Q15 state consumed by the range decoder.
uint expected = (uint)Av1Distribution.ProbabilityTop - forwardThresholds[group]; uint expected = (uint)Av1Distribution.ProbabilityTop - forwardThresholds[group];
@ -155,7 +155,7 @@ public class Av1InterIntraEntropyTests
/// <returns>Every decoded block size paired with its size group.</returns> /// <returns>Every decoded block size paired with its size group.</returns>
public static TheoryData<int, int> GetBlockSizeGroups() public static TheoryData<int, int> GetBlockSizeGroups()
{ {
// These are the explicit Size_Group values from AV1 section 9.3 and libaom common_data.h. The test keeps the // These are the explicit Size_Group values from AV1 section 9.3 and the normative lookup table. The test keeps the
// expected table independent from the production geometry formula so a shared calculation cannot mask errors. // expected table independent from the production geometry formula so a shared calculation cannot mask errors.
int[] sizeGroups = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 0, 0, 1, 1, 2, 2]; int[] sizeGroups = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 0, 0, 1, 1, 2, 2];
TheoryData<int, int> result = []; TheoryData<int, int> result = [];

8
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterModeEntropyTests.cs

@ -14,10 +14,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
public class Av1InterModeEntropyTests public class Av1InterModeEntropyTests
{ {
/// <summary> /// <summary>
/// Verifies the normative single-reference inter-mode distributions against libaom's forward Q15 defaults. /// Verifies the normative single-reference inter-mode distributions against the reference decoder's forward Q15 defaults.
/// </summary> /// </summary>
[Fact] [Fact]
public void InterModeDefaultsMatchLibaom() public void InterModeDefaultsMatchReference()
{ {
AssertBinaryDefaults(Av1DefaultDistributions.NewMv, [24035, 16630, 15339, 8386, 12222, 4676]); AssertBinaryDefaults(Av1DefaultDistributions.NewMv, [24035, 16630, 15339, 8386, 12222, 4676]);
AssertBinaryDefaults(Av1DefaultDistributions.ZeroMv, [2175, 1054]); AssertBinaryDefaults(Av1DefaultDistributions.ZeroMv, [2175, 1054]);
@ -36,7 +36,7 @@ public class Av1InterModeEntropyTests
[InlineData(0, 0, 0, 0)] [InlineData(0, 0, 0, 0)]
[InlineData(77, 5, 1, 4)] [InlineData(77, 5, 1, 4)]
[InlineData(93, 5, 1, 5)] [InlineData(93, 5, 1, 5)]
public void PackedInterModeContextMatchesLibaom(int modeContext, int expectedNewMv, int expectedZeroMv, int expectedRefMv) public void PackedInterModeContextMatchesReference(int modeContext, int expectedNewMv, int expectedZeroMv, int expectedRefMv)
{ {
Assert.Equal(expectedNewMv, Av1SymbolContextHelper.GetNewMvContext(modeContext)); Assert.Equal(expectedNewMv, Av1SymbolContextHelper.GetNewMvContext(modeContext));
Assert.Equal(expectedZeroMv, Av1SymbolContextHelper.GetZeroMvContext(modeContext)); Assert.Equal(expectedZeroMv, Av1SymbolContextHelper.GetZeroMvContext(modeContext));
@ -55,7 +55,7 @@ public class Av1InterModeEntropyTests
[InlineData((int)Av1PredictionMode.GlobalMotionVector, 1, 0, -1)] [InlineData((int)Av1PredictionMode.GlobalMotionVector, 1, 0, -1)]
[InlineData((int)Av1PredictionMode.NearestMotionVector, 1, 1, 0)] [InlineData((int)Av1PredictionMode.NearestMotionVector, 1, 1, 0)]
[InlineData((int)Av1PredictionMode.NearMotionVector, 1, 1, 1)] [InlineData((int)Av1PredictionMode.NearMotionVector, 1, 1, 1)]
public void ReadInterModeMatchesLibaomTree(int expectedMode, int newMvSymbol, int zeroMvSymbol, int refMvSymbol) public void ReadInterModeMatchesReference(int expectedMode, int newMvSymbol, int zeroMvSymbol, int refMvSymbol)
{ {
const int modeContext = 77; const int modeContext = 77;
Av1Distribution newMv = Av1DefaultDistributions.NewMv[5]; Av1Distribution newMv = Av1DefaultDistributions.NewMv[5];

12
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterPredictorTests.cs

@ -91,14 +91,14 @@ public class Av1InterPredictorTests
/// Verifies exact 8-bit copy and convolution output, scalar tails, and untouched destination padding under every SIMD configuration. /// Verifies exact 8-bit copy and convolution output, scalar tails, and untouched destination padding under every SIMD configuration.
/// </summary> /// </summary>
[Fact] [Fact]
public void BytePredictionMatchesLibaomOracleAcrossIntrinsicWidths() public void BytePredictionMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateBytePredictions, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateBytePredictions, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies exact 8-, 10-, and 12-bit ushort output, scalar tails, and untouched destination padding under every SIMD configuration. /// Verifies exact 8-, 10-, and 12-bit ushort output, scalar tails, and untouched destination padding under every SIMD configuration.
/// </summary> /// </summary>
[Fact] [Fact]
public void HighBitDepthPredictionMatchesLibaomOracleAcrossIntrinsicWidths() public void HighBitDepthPredictionMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthPredictions, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthPredictions, PredictorConfigurations);
/// <summary> /// <summary>
@ -334,7 +334,7 @@ public class Av1InterPredictorTests
: new short[Math.Max(testCase.Width, 16) * (testCase.Height + FilterTapCount - 1)]; : new short[Math.Max(testCase.Width, 16) * (testCase.Height + FilterTapCount - 1)];
/// <summary> /// <summary>
/// Applies libaom's single-reference copy or convolution equations to an 8-bit prediction block. /// Applies the reference decoder's single-reference copy or convolution equations to an 8-bit prediction block.
/// </summary> /// </summary>
/// <param name="source">The complete padded reference plane.</param> /// <param name="source">The complete padded reference plane.</param>
/// <param name="sourceStride">The source-row stride.</param> /// <param name="sourceStride">The source-row stride.</param>
@ -411,7 +411,7 @@ public class Av1InterPredictorTests
} }
/// <summary> /// <summary>
/// Applies libaom's single-reference copy or convolution equations to a high-bit-depth prediction block. /// Applies the reference decoder's single-reference copy or convolution equations to a high-bit-depth prediction block.
/// </summary> /// </summary>
/// <param name="source">The complete padded reference plane.</param> /// <param name="source">The complete padded reference plane.</param>
/// <param name="sourceStride">The source-row stride.</param> /// <param name="sourceStride">The source-row stride.</param>
@ -489,7 +489,7 @@ public class Av1InterPredictorTests
} }
/// <summary> /// <summary>
/// Applies libaom's biased two-pass 8-bit convolution and removes both intermediate bias terms after vertical filtering. /// Applies the reference decoder's biased two-pass 8-bit convolution and removes both intermediate bias terms after vertical filtering.
/// </summary> /// </summary>
/// <param name="source">The complete padded reference plane.</param> /// <param name="source">The complete padded reference plane.</param>
/// <param name="sourceStride">The source-row stride.</param> /// <param name="sourceStride">The source-row stride.</param>
@ -531,7 +531,7 @@ public class Av1InterPredictorTests
} }
/// <summary> /// <summary>
/// Applies libaom's biased two-pass high-bit-depth convolution and removes both intermediate bias terms after vertical filtering. /// Applies the reference decoder's biased two-pass high-bit-depth convolution and removes both intermediate bias terms after vertical filtering.
/// </summary> /// </summary>
/// <param name="source">The complete padded reference plane.</param> /// <param name="source">The complete padded reference plane.</param>
/// <param name="sourceStride">The source-row stride.</param> /// <param name="sourceStride">The source-row stride.</param>

8
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterpolationFilterEntropyTests.cs

@ -31,7 +31,7 @@ public class Av1InterpolationFilterEntropyTests
private const int HorizontalDirection = 1; private const int HorizontalDirection = 1;
/// <summary> /// <summary>
/// Gets libaom's forward Q15 switchable interpolation-filter thresholds in context order. /// Gets the reference decoder's forward Q15 switchable interpolation-filter thresholds in context order.
/// </summary> /// </summary>
private static ReadOnlySpan<ushort> ForwardThresholds => private static ReadOnlySpan<ushort> ForwardThresholds =>
[ [
@ -54,10 +54,10 @@ public class Av1InterpolationFilterEntropyTests
]; ];
/// <summary> /// <summary>
/// Verifies every normative switchable interpolation-filter distribution against libaom's forward Q15 defaults. /// Verifies every normative switchable interpolation-filter distribution against the reference decoder's forward Q15 defaults.
/// </summary> /// </summary>
[Fact] [Fact]
public void DefaultsMatchLibaom() public void DefaultsMatchReference()
{ {
const int thresholdCount = 2; const int thresholdCount = 2;
ReadOnlySpan<ushort> forwardThresholds = ForwardThresholds; ReadOnlySpan<ushort> forwardThresholds = ForwardThresholds;
@ -118,7 +118,7 @@ public class Av1InterpolationFilterEntropyTests
/// Verifies all sixteen combinations of reference type, direction, and contributing neighbor filter state. /// Verifies all sixteen combinations of reference type, direction, and contributing neighbor filter state.
/// </summary> /// </summary>
[Fact] [Fact]
public void ContextLayoutMatchesLibaom() public void ContextLayoutMatchesReference()
{ {
Av1BlockModeInfo single = CreateModeInfo( Av1BlockModeInfo single = CreateModeInfo(
Av1ReferenceFrameType.Last, Av1ReferenceFrameType.Last,

4
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraBlockCopyPredictorTests.cs

@ -23,14 +23,14 @@ public class Av1IntraBlockCopyPredictorTests
/// Verifies all four source phases for 8-bit samples at every AV1 transform size. /// Verifies all four source phases for 8-bit samples at every AV1 transform size.
/// </summary> /// </summary>
[Fact] [Fact]
public void EightBitPredictionMatchesScalarAcrossIntrinsicWidths() public void EightBitPredictionMatchesScalar()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateEightBitPrediction, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateEightBitPrediction, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies all four source phases for high-bit-depth samples at every AV1 transform size. /// Verifies all four source phases for high-bit-depth samples at every AV1 transform size.
/// </summary> /// </summary>
[Fact] [Fact]
public void HighBitDepthPredictionMatchesScalarAcrossIntrinsicWidths() public void HighBitDepthPredictionMatchesScalar()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthPrediction, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthPrediction, PredictorConfigurations);
/// <summary> /// <summary>

2
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraBlockCopyTests.cs

@ -18,7 +18,7 @@ public class Av1IntraBlockCopyTests
/// Verifies the horizontal fallback used in the tile's first superblock row. /// Verifies the horizontal fallback used in the tile's first superblock row.
/// </summary> /// </summary>
[Fact] [Fact]
public void FindReferenceUsesDelayedHorizontalFallbackInFirstSuperblockRow() public void FindReferenceUsesFirstRowFallback()
{ {
ObuSequenceHeader sequenceHeader = CreateSequenceHeader(); ObuSequenceHeader sequenceHeader = CreateSequenceHeader();
Av1FrameInfo frameInfo = new(sequenceHeader); Av1FrameInfo frameInfo = new(sequenceHeader);

104
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraPredictionMemory.cs

@ -1,104 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
internal class Av1IntraPredictionMemory
{
public const int Padding = 16;
private const int TopPadding = 7;
private const int MaxBlockSizeTimes2 = 1 << Av1Constants.MaxSuperBlockSizeLog2;
private const int TotalPixelCount = 4096;
private readonly byte[] destination = new byte[TotalPixelCount];
private readonly byte[] referenceSource = new byte[TotalPixelCount];
private readonly byte[] leftMemory = new byte[MaxBlockSizeTimes2 + Padding];
private readonly byte[] topMemory = new byte[MaxBlockSizeTimes2 + Padding + TopPadding];
private readonly int bitDepth;
public Av1IntraPredictionMemory(int bitDepth) => this.bitDepth = bitDepth;
public Span<byte> Destination => this.destination;
public Span<byte> Left => this.leftMemory;
public Span<byte> Top => this.topMemory;
/// <summary>
/// Sets referenceSource, left and top to <paramref name="value"/>.
/// </summary>
public void Set(byte value)
{
for (int r = 0; r < this.referenceSource.Length; r++)
{
this.referenceSource[r] = value;
}
// Upsampling in the directional predictors extends left/top[-1] to [-2].
for (int i = Padding - 2; i < Padding + MaxBlockSizeTimes2; ++i)
{
this.leftMemory[i] = this.topMemory[i] = value;
}
}
public void Scramble(Random rnd)
{
for (int i = 0; i < this.referenceSource.Length; i++)
{
this.referenceSource[i] = this.GetRandomValue(rnd);
}
for (int i = Padding; i < (MaxBlockSizeTimes2 >> 1) + Padding; ++i)
{
this.leftMemory[i] = this.GetRandomValue(rnd);
}
for (int i = Padding - 1; i < (MaxBlockSizeTimes2 >> 1) + Padding; ++i)
{
this.topMemory[i] = this.GetRandomValue(rnd);
}
// Some directional predictors require top-right, bottom-left.
for (int i = MaxBlockSizeTimes2 >> 1; i < MaxBlockSizeTimes2; ++i)
{
this.leftMemory[Padding + i] = this.GetRandomValue(rnd);
this.topMemory[Padding + i] = this.GetRandomValue(rnd);
}
// TODO(jzern): reorder this and regenerate the digests after switching
// random number generators.
// Upsampling in the directional predictors extends left/top[-1] to [-2].
this.leftMemory[Padding - 1] = this.GetRandomValue(rnd);
this.leftMemory[Padding - 2] = this.GetRandomValue(rnd);
this.topMemory[Padding - 2] = this.GetRandomValue(rnd);
this.leftMemory.AsSpan(0, Padding - 2).Clear();
this.topMemory.AsSpan(0, Padding - 2).Clear();
this.topMemory.AsSpan(MaxBlockSizeTimes2 + Padding, TopPadding).Clear();
}
public void CopySourceToDestination() => this.referenceSource.CopyTo(this.destination.AsSpan());
/// <summary>
/// Return a hash string of a block of bytes.
/// </summary>
/// <remarks>This test design is used extensively in 'libgav1' tests, some of which are ported here.</remarks>
public static string GetDigest(byte[] input)
{
byte[] hash = MD5.HashData(input);
StringBuilder sb = new();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2", CultureInfo.InvariantCulture));
}
return sb.ToString();
}
public string GetDestinationDigest() => GetDigest(this.destination);
private byte GetRandomValue(Random random) => (byte)(random.Next(1 << 16) & ((1 << this.bitDepth) - 1));
}

2
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseQuantizationTests.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
public class Av1InverseQuantizationTests public class Av1InverseQuantizationTests
{ {
[Fact] [Fact]
public void QuantizationMatricesCoverAllLevelsPlanesAndTransformSizes() public void MatricesCoverAllLevelsPlanesAndTransformSizes()
{ {
for (int level = 0; level < Av1Constants.QuantificationMatrixLevelCount; level++) for (int level = 0; level < Av1Constants.QuantificationMatrixLevelCount; level++)
{ {

12
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs

@ -43,10 +43,10 @@ public class Av1InverseTransformTests
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertIdentityOperatorParity, TransformConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertIdentityOperatorParity, TransformConfigurations);
/// <summary> /// <summary>
/// Verifies the current-libaom widened operations at the twelve-bit inverse row-stage bounds. /// Verifies the reference widened operations at the twelve-bit inverse row-stage bounds.
/// </summary> /// </summary>
[Fact] [Fact]
public void TwelveBitWideIntermediatesMatchCurrentLibaom() public void TwelveBitWideIntermediatesMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertTwelveBitWideIntermediateParity, TransformConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertTwelveBitWideIntermediateParity, TransformConfigurations);
/// <summary> /// <summary>
@ -123,7 +123,7 @@ public class Av1InverseTransformTests
cosBit, cosBit,
stageRange); stageRange);
// These are the exact outputs of current libaom's signed Int64 terminal round. The first positive lane has an // These are the exact outputs of the reference decoder's signed Int64 terminal round. The first positive lane has an
// Int32 fixed-point sum of 2,147,482,471, so adding the 2,048 rounding bias in Int32 would wrap. // Int32 fixed-point sum of 2,147,482,471, so adding the 2,048 rounding bias in Int32 would wrap.
Vector128<int> adstExpected0 = Vector128.Create(524_288, -524_288, 524_287, -524_287); Vector128<int> adstExpected0 = Vector128.Create(524_288, -524_288, 524_287, -524_287);
Vector128<int> adstExpected1 = Vector128.Create(33_612, -33_612, 33_612, -33_612); Vector128<int> adstExpected1 = Vector128.Create(33_612, -33_612, 33_612, -33_612);
@ -167,7 +167,7 @@ public class Av1InverseTransformTests
} }
/// <summary> /// <summary>
/// Verifies one identity operator against exact current-libaom widened fixed-point results. /// Verifies one identity operator against exact reference widened fixed-point results.
/// </summary> /// </summary>
/// <typeparam name="TOperator">The inverse identity operator.</typeparam> /// <typeparam name="TOperator">The inverse identity operator.</typeparam>
/// <param name="length">The identity-transform length.</param> /// <param name="length">The identity-transform length.</param>
@ -291,7 +291,7 @@ public class Av1InverseTransformTests
/// <param name="bitDepth">The coded sample bit depth.</param> /// <param name="bitDepth">The coded sample bit depth.</param>
[Theory] [Theory]
[MemberData(nameof(Av1ForwardTransformTests.ValidTransformCases), MemberType = typeof(Av1ForwardTransformTests))] [MemberData(nameof(Av1ForwardTransformTests.ValidTransformCases), MemberType = typeof(Av1ForwardTransformTests))]
public void TwoDimensionalSimdKernelsMatchScalarForEveryValidConfiguration( public void TwoDimensionalKernelsMatchReference(
int transformTypeValue, int transformTypeValue,
int transformSizeValue, int transformSizeValue,
int bitDepth) int bitDepth)
@ -306,7 +306,7 @@ public class Av1InverseTransformTests
/// Verifies lossless inverse Walsh-Hadamard reconstruction against an independent definition. /// Verifies lossless inverse Walsh-Hadamard reconstruction against an independent definition.
/// </summary> /// </summary>
[Fact] [Fact]
public void LosslessWalshHadamardMatchesReferenceAcrossIntrinsicTiers() public void LosslessWalshHadamardMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertLosslessWalshHadamardParity, TransformConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertLosslessWalshHadamardParity, TransformConfigurations);
/// <summary> /// <summary>

12
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeEntropyTests.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
public class Av1MotionModeEntropyTests public class Av1MotionModeEntropyTests
{ {
/// <summary> /// <summary>
/// Gets libaom's forward Q15 Simple Translation, OBMC, and Warped thresholds in block-size order. /// Gets the reference decoder's forward Q15 Simple Translation, OBMC, and Warped thresholds in block-size order.
/// </summary> /// </summary>
private static ReadOnlySpan<ushort> MotionModeForwardThresholds => private static ReadOnlySpan<ushort> MotionModeForwardThresholds =>
[ [
@ -45,7 +45,7 @@ public class Av1MotionModeEntropyTests
]; ];
/// <summary> /// <summary>
/// Gets libaom's forward Q15 Simple Translation and OBMC thresholds in block-size order. /// Gets the reference decoder's forward Q15 Simple Translation and OBMC thresholds in block-size order.
/// </summary> /// </summary>
private static ReadOnlySpan<ushort> ObmcForwardThresholds => private static ReadOnlySpan<ushort> ObmcForwardThresholds =>
[ [
@ -54,10 +54,10 @@ public class Av1MotionModeEntropyTests
]; ];
/// <summary> /// <summary>
/// Verifies all twenty-two ternary and binary motion-mode distributions against libaom's forward Q15 defaults. /// Verifies all twenty-two ternary and binary motion-mode distributions against the reference decoder's forward Q15 defaults.
/// </summary> /// </summary>
[Fact] [Fact]
public void DefaultsMatchLibaomForEveryBlockSize() public void DefaultsMatchReference()
{ {
const int blockSizeCount = (int)Av1BlockSize.AllSizes; const int blockSizeCount = (int)Av1BlockSize.AllSizes;
const int ternaryThresholdCount = 2; const int ternaryThresholdCount = 2;
@ -78,7 +78,7 @@ public class Av1MotionModeEntropyTests
for (int threshold = 0; threshold < ternaryThresholdCount; threshold++) for (int threshold = 0; threshold < ternaryThresholdCount; threshold++)
{ {
// Av1Distribution stores inverse cumulative thresholds, so complement libaom's published forward // Av1Distribution stores inverse cumulative thresholds, so complement the reference decoder's published forward
// Q15 values before comparing the exact state consumed by the range decoder. // Q15 values before comparing the exact state consumed by the range decoder.
uint expected = (uint)Av1Distribution.ProbabilityTop - uint expected = (uint)Av1Distribution.ProbabilityTop -
motionModeForwardThresholds[(blockSize * ternaryThresholdCount) + threshold]; motionModeForwardThresholds[(blockSize * ternaryThresholdCount) + threshold];
@ -200,7 +200,7 @@ public class Av1MotionModeEntropyTests
[Theory] [Theory]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]
public void ReadMotionModePreservesFollowingInterpolationSymbolAlignment(bool allowWarpedMotion) public void PreservesFollowingInterpolationSymbol(bool allowWarpedMotion)
{ {
Av1BlockSize blockSize = Av1BlockSize.Block16x16; Av1BlockSize blockSize = Av1BlockSize.Block16x16;
const int interpolationContext = 3; const int interpolationContext = 3;

6
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionModeInfoTests.cs

@ -24,7 +24,7 @@ public class Av1MotionModeInfoTests
/// Verifies that an extended 8x32 rectangle omits inter-intra syntax and reads the following interpolation filter. /// Verifies that an extended 8x32 rectangle omits inter-intra syntax and reads the following interpolation filter.
/// </summary> /// </summary>
[Fact] [Fact]
public void ReadInterFrameModeInfoOmitsInterIntraFlagForExtendedRectangle() public void OmitsInterIntraForExtendedRectangle()
{ {
ObuSequenceHeader sequenceHeader = CreateSequenceHeader(); ObuSequenceHeader sequenceHeader = CreateSequenceHeader();
sequenceHeader.EnableInterIntraCompound = true; sequenceHeader.EnableInterIntraCompound = true;
@ -66,7 +66,7 @@ public class Av1MotionModeInfoTests
[InlineData(true, true, (int)Av1MotionMode.SimpleTranslation)] [InlineData(true, true, (int)Av1MotionMode.SimpleTranslation)]
[InlineData(true, false, (int)Av1MotionMode.Obmc)] [InlineData(true, false, (int)Av1MotionMode.Obmc)]
[InlineData(true, true, (int)Av1MotionMode.Obmc)] [InlineData(true, true, (int)Av1MotionMode.Obmc)]
public void ReadInterFrameModeInfoContinuesFromFalseInterIntraThroughMotionModeIntoInterpolation( public void ContinuesFromInterIntraToInterpolation(
bool isMotionModeSwitchable, bool isMotionModeSwitchable,
bool allowWarpedMotion, bool allowWarpedMotion,
int selectedMotionModeValue) int selectedMotionModeValue)
@ -158,7 +158,7 @@ public class Av1MotionModeInfoTests
[Theory] [Theory]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]
public void ReadInterFrameModeInfoReadsSelectedInterIntraBeforeInterpolation(bool useWedge) public void ReadsInterIntraBeforeInterpolation(bool useWedge)
{ {
ObuSequenceHeader sequenceHeader = CreateSequenceHeader(); ObuSequenceHeader sequenceHeader = CreateSequenceHeader();
sequenceHeader.EnableInterIntraCompound = true; sequenceHeader.EnableInterIntraCompound = true;

4
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionVectorEntropyTests.cs

@ -14,10 +14,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
public class Av1MotionVectorEntropyTests public class Av1MotionVectorEntropyTests
{ {
/// <summary> /// <summary>
/// Verifies both motion-vector contexts against every normative forward Q15 default from libaom. /// Verifies both motion-vector contexts against every normative forward Q15 default from the reference decoder.
/// </summary> /// </summary>
[Fact] [Fact]
public void MotionVectorDefaultsMatchLibaom() public void MotionVectorDefaultsMatchReference()
{ {
Av1FrameEntropyContext context = new(0); Av1FrameEntropyContext context = new(0);

4
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionVectorTests.cs

@ -90,7 +90,7 @@ public class Av1MotionVectorTests
/// Verifies the complete-block and sixteen-sample borders used to clamp spatial reference candidates. /// Verifies the complete-block and sixteen-sample borders used to clamp spatial reference candidates.
/// </summary> /// </summary>
[Fact] [Fact]
public void ClampReferenceMatchesLibaomSpatialLimits() public void ClampReferenceMatchesSpatialLimits()
{ {
const int blockWidth = 16; const int blockWidth = 16;
const int blockHeight = 8; const int blockHeight = 8;
@ -143,7 +143,7 @@ public class Av1MotionVectorTests
[InlineData(2, -2, 1, 3, 1, -1)] [InlineData(2, -2, 1, 3, 1, -1)]
[InlineData(31, -31, 40, 40, 31, -31)] [InlineData(31, -31, 40, 40, 31, -31)]
[InlineData(4095, -4095, 31, 1, 16383, -16383)] [InlineData(4095, -4095, 31, 1, 16383, -16383)]
public void ProjectTemporalMatchesLibaom( public void ProjectTemporalMatchesReference(
int row, int row,
int column, int column,
int numerator, int numerator,

2
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PalettePredictorTests.cs

@ -24,7 +24,7 @@ public class Av1PalettePredictorTests
/// Verifies exact indexed reconstruction and destination-padding preservation for every palette size. /// Verifies exact indexed reconstruction and destination-padding preservation for every palette size.
/// </summary> /// </summary>
[Fact] [Fact]
public void PredictMatchesIndependentDefinitionAcrossIntrinsicWidths() public void PredictMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePredictors, Configurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePredictors, Configurations);
/// <summary> /// <summary>

24
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs

@ -47,77 +47,77 @@ public class Av1PredictorTests
/// Verifies DC prediction with each register-width tier and the scalar fallback. /// Verifies DC prediction with each register-width tier and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void DcPredictorsMatchScalarDefinitionsAcrossIntrinsicWidths() public void DcPredictorsMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateDcPredictors, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateDcPredictors, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies horizontal prediction with each register-width tier and the scalar fallback. /// Verifies horizontal prediction with each register-width tier and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void HorizontalPredictorMatchesScalarDefinitionAcrossIntrinsicWidths() public void HorizontalPredictorMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHorizontalPredictor, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHorizontalPredictor, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies vertical prediction with each register-width tier and the scalar fallback. /// Verifies vertical prediction with each register-width tier and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void VerticalPredictorMatchesScalarDefinitionAcrossIntrinsicWidths() public void VerticalPredictorMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateVerticalPredictor, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateVerticalPredictor, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies Paeth prediction with each register-width tier and the scalar fallback. /// Verifies Paeth prediction with each register-width tier and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void PaethPredictorMatchesScalarDefinitionAcrossIntrinsicWidths() public void PaethPredictorMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePaethPredictor, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePaethPredictor, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies smooth prediction with each register-width tier and the scalar fallback. /// Verifies smooth prediction with each register-width tier and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void SmoothPredictorMatchesScalarDefinitionAcrossIntrinsicWidths() public void SmoothPredictorMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateSmoothPredictor, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateSmoothPredictor, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies horizontal smooth prediction with each register-width tier and the scalar fallback. /// Verifies horizontal smooth prediction with each register-width tier and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void SmoothHorizontalPredictorMatchesScalarDefinitionAcrossIntrinsicWidths() public void SmoothHorizontalPredictorMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateSmoothHorizontalPredictor, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateSmoothHorizontalPredictor, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies vertical smooth prediction with each register-width tier and the scalar fallback. /// Verifies vertical smooth prediction with each register-width tier and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void SmoothVerticalPredictorMatchesScalarDefinitionAcrossIntrinsicWidths() public void SmoothVerticalPredictorMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateSmoothVerticalPredictor, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateSmoothVerticalPredictor, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies directional prediction with each register-width tier and the scalar fallback. /// Verifies directional prediction with each register-width tier and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void DirectionalPredictorsMatchScalarDefinitionsAcrossIntrinsicWidths() public void DirectionalPredictorsMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateDirectionalPredictors, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateDirectionalPredictors, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies filter-intra prediction with each register-width tier and the scalar fallback. /// Verifies filter-intra prediction with each register-width tier and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void FilterIntraPredictorsMatchScalarDefinitionsAcrossIntrinsicWidths() public void FilterIntraPredictorsMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateFilterIntraPredictors, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateFilterIntraPredictors, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies intra-edge upsampling with Vector128 and the scalar fallback. /// Verifies intra-edge upsampling with Vector128 and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void EdgeUpsamplingMatchesScalarDefinitionsAcrossIntrinsicWidths() public void EdgeUpsamplingMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateEdgeUpsampling, HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateEdgeUpsampling, HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic);
/// <summary> /// <summary>
/// Verifies intra-edge filtering with Vector128 and the scalar fallback. /// Verifies intra-edge filtering with Vector128 and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void EdgeFilteringMatchesScalarDefinitionsAcrossIntrinsicWidths() public void EdgeFilteringMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateEdgeFiltering, HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateEdgeFiltering, HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic);
/// <summary> /// <summary>
@ -523,7 +523,7 @@ public class Av1PredictorTests
} }
/// <summary> /// <summary>
/// Retains byte-exact libaom vectors so scalar and SIMD code cannot share the same mistranslation unnoticed. /// Retains byte-exact reference vectors so scalar and SIMD code cannot share the same mistranslation unnoticed.
/// </summary> /// </summary>
private static void ValidateKnownFilterIntraVectors() private static void ValidateKnownFilterIntraVectors()
{ {

291
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs

@ -158,137 +158,137 @@ public class Av1ReconstructionConformanceTests
private const int AverageCompoundFixtureFrameCount = 19; private const int AverageCompoundFixtureFrameCount = 19;
/// <summary> /// <summary>
/// The number of shown frames in the official libaom all-intra sequence. /// The number of shown frames in the official AV1 all-intra sequence.
/// </summary> /// </summary>
private const int OfficialAllIntraFixtureFrameCount = 39; private const int OfficialAllIntraFixtureFrameCount = 39;
/// <summary> /// <summary>
/// The number of shown frames in the official libaom CDF-update sequence. /// The number of shown frames in the official AV1 CDF-update sequence.
/// </summary> /// </summary>
private const int OfficialCdfUpdateFixtureFrameCount = 2; private const int OfficialCdfUpdateFixtureFrameCount = 2;
/// <summary> /// <summary>
/// The number of shown frames in the official libaom temporal motion-field sequence. /// The number of shown frames in the official AV1 temporal motion-field sequence.
/// </summary> /// </summary>
private const int OfficialMotionFieldFixtureFrameCount = 4; private const int OfficialMotionFieldFixtureFrameCount = 4;
/// <summary> /// <summary>
/// The displayed width of the official libaom extreme-displacement intra-block-copy sequence. /// The displayed width of the official AV1 extreme-displacement intra-block-copy sequence.
/// </summary> /// </summary>
private const int OfficialIntraBlockCopyFixtureWidth = 1920; private const int OfficialIntraBlockCopyFixtureWidth = 1920;
/// <summary> /// <summary>
/// The displayed height of the official libaom extreme-displacement intra-block-copy sequence. /// The displayed height of the official AV1 extreme-displacement intra-block-copy sequence.
/// </summary> /// </summary>
private const int OfficialIntraBlockCopyFixtureHeight = 1080; private const int OfficialIntraBlockCopyFixtureHeight = 1080;
/// <summary> /// <summary>
/// The number of shown frames in the official libaom extreme-displacement intra-block-copy sequence. /// The number of shown frames in the official AV1 extreme-displacement intra-block-copy sequence.
/// </summary> /// </summary>
private const int OfficialIntraBlockCopyFixtureFrameCount = 2; private const int OfficialIntraBlockCopyFixtureFrameCount = 2;
/// <summary> /// <summary>
/// The displayed width of the official libaom two-spatial-layer sequence. /// The displayed width of the official AV1 two-spatial-layer sequence.
/// </summary> /// </summary>
private const int OfficialTwoSpatialLayerFixtureWidth = 1280; private const int OfficialTwoSpatialLayerFixtureWidth = 1280;
/// <summary> /// <summary>
/// The displayed height of the official libaom two-spatial-layer sequence. /// The displayed height of the official AV1 two-spatial-layer sequence.
/// </summary> /// </summary>
private const int OfficialTwoSpatialLayerFixtureHeight = 720; private const int OfficialTwoSpatialLayerFixtureHeight = 720;
/// <summary> /// <summary>
/// The number of default-operating-point frames in the official libaom two-spatial-layer sequence. /// The number of default-operating-point frames in the official AV1 two-spatial-layer sequence.
/// </summary> /// </summary>
private const int OfficialTwoSpatialLayerFixtureFrameCount = 8; private const int OfficialTwoSpatialLayerFixtureFrameCount = 8;
/// <summary> /// <summary>
/// The displayed width of the official libaom two-temporal-layer sequence. /// The displayed width of the official AV1 two-temporal-layer sequence.
/// </summary> /// </summary>
private const int OfficialTwoTemporalLayerFixtureWidth = 640; private const int OfficialTwoTemporalLayerFixtureWidth = 640;
/// <summary> /// <summary>
/// The displayed height of the official libaom two-temporal-layer sequence. /// The displayed height of the official AV1 two-temporal-layer sequence.
/// </summary> /// </summary>
private const int OfficialTwoTemporalLayerFixtureHeight = 360; private const int OfficialTwoTemporalLayerFixtureHeight = 360;
/// <summary> /// <summary>
/// The number of default-operating-point frames in the official libaom two-temporal-layer sequence. /// The number of default-operating-point frames in the official AV1 two-temporal-layer sequence.
/// </summary> /// </summary>
private const int OfficialTwoTemporalLayerFixtureFrameCount = 8; private const int OfficialTwoTemporalLayerFixtureFrameCount = 8;
/// <summary> /// <summary>
/// The displayed width of the official libaom spatial-and-temporal-layer sequence. /// The displayed width of the official AV1 spatial-and-temporal-layer sequence.
/// </summary> /// </summary>
private const int OfficialSpatialTemporalLayerFixtureWidth = 1280; private const int OfficialSpatialTemporalLayerFixtureWidth = 1280;
/// <summary> /// <summary>
/// The displayed height of the official libaom spatial-and-temporal-layer sequence. /// The displayed height of the official AV1 spatial-and-temporal-layer sequence.
/// </summary> /// </summary>
private const int OfficialSpatialTemporalLayerFixtureHeight = 720; private const int OfficialSpatialTemporalLayerFixtureHeight = 720;
/// <summary> /// <summary>
/// The number of default-operating-point frames in the official libaom spatial-and-temporal-layer sequence. /// The number of default-operating-point frames in the official AV1 spatial-and-temporal-layer sequence.
/// </summary> /// </summary>
private const int OfficialSpatialTemporalLayerFixtureFrameCount = 8; private const int OfficialSpatialTemporalLayerFixtureFrameCount = 8;
/// <summary> /// <summary>
/// The number of frames in the official libaom active-film-grain sequence. /// The number of frames in the official AV1 active-film-grain sequence.
/// </summary> /// </summary>
private const int OfficialFilmGrainFixtureFrameCount = 10; private const int OfficialFilmGrainFixtureFrameCount = 10;
/// <summary> /// <summary>
/// The width of the official libaom eight-bit monochrome sequence. /// The width of the official AV1 eight-bit monochrome sequence.
/// </summary> /// </summary>
private const int OfficialMonochromeFixtureWidth = 320; private const int OfficialMonochromeFixtureWidth = 320;
/// <summary> /// <summary>
/// The height of the official libaom eight-bit monochrome sequence. /// The height of the official AV1 eight-bit monochrome sequence.
/// </summary> /// </summary>
private const int OfficialMonochromeFixtureHeight = 180; private const int OfficialMonochromeFixtureHeight = 180;
/// <summary> /// <summary>
/// The number of frames in the official libaom eight-bit monochrome sequence. /// The number of frames in the official AV1 eight-bit monochrome sequence.
/// </summary> /// </summary>
private const int OfficialMonochromeFixtureFrameCount = 10; private const int OfficialMonochromeFixtureFrameCount = 10;
/// <summary> /// <summary>
/// The width of the official libaom eight-bit quantizer-boundary sequences. /// The width of the official AV1 eight-bit quantizer-boundary sequences.
/// </summary> /// </summary>
private const int OfficialEightBitQuantizerFixtureWidth = 352; private const int OfficialEightBitQuantizerFixtureWidth = 352;
/// <summary> /// <summary>
/// The height of the official libaom eight-bit quantizer-boundary sequences. /// The height of the official AV1 eight-bit quantizer-boundary sequences.
/// </summary> /// </summary>
private const int OfficialEightBitQuantizerFixtureHeight = 288; private const int OfficialEightBitQuantizerFixtureHeight = 288;
/// <summary> /// <summary>
/// The width of the official libaom ten-bit quantizer-boundary sequences. /// The width of the official AV1 ten-bit quantizer-boundary sequences.
/// </summary> /// </summary>
private const int OfficialTenBitQuantizerFixtureWidth = 640; private const int OfficialTenBitQuantizerFixtureWidth = 640;
/// <summary> /// <summary>
/// The height of the official libaom ten-bit quantizer-boundary sequences. /// The height of the official AV1 ten-bit quantizer-boundary sequences.
/// </summary> /// </summary>
private const int OfficialTenBitQuantizerFixtureHeight = 360; private const int OfficialTenBitQuantizerFixtureHeight = 360;
/// <summary> /// <summary>
/// The number of frames in each official libaom quantizer-boundary sequence. /// The number of frames in each official AV1 quantizer-boundary sequence.
/// </summary> /// </summary>
private const int OfficialQuantizerFixtureFrameCount = 2; private const int OfficialQuantizerFixtureFrameCount = 2;
/// <summary> /// <summary>
/// The minimum dimension retained from the official libaom frame-size matrix. /// The minimum dimension retained from the official AV1 frame-size matrix.
/// </summary> /// </summary>
private const int OfficialFrameSizeFixtureMinimumDimension = 196; private const int OfficialFrameSizeFixtureMinimumDimension = 196;
/// <summary> /// <summary>
/// The maximum dimension retained from the official libaom frame-size matrix. /// The maximum dimension retained from the official AV1 frame-size matrix.
/// </summary> /// </summary>
private const int OfficialFrameSizeFixtureMaximumDimension = 226; private const int OfficialFrameSizeFixtureMaximumDimension = 226;
/// <summary> /// <summary>
/// The number of frames in each official libaom frame-size sequence. /// The number of frames in each official AV1 frame-size sequence.
/// </summary> /// </summary>
private const int OfficialFrameSizeFixtureFrameCount = 2; private const int OfficialFrameSizeFixtureFrameCount = 2;
@ -330,17 +330,17 @@ public class Av1ReconstructionConformanceTests
(1 << (int)Av1TransformType.HorizontalDct); (1 << (int)Av1TransformType.HorizontalDct);
/// <summary> /// <summary>
/// The displayed width of the official libaom motion-vector sequence. /// The displayed width of the official AV1 motion-vector sequence.
/// </summary> /// </summary>
private const int OfficialMotionVectorFixtureWidth = 352; private const int OfficialMotionVectorFixtureWidth = 352;
/// <summary> /// <summary>
/// The displayed height of the official libaom motion-vector sequence. /// The displayed height of the official AV1 motion-vector sequence.
/// </summary> /// </summary>
private const int OfficialMotionVectorFixtureHeight = 288; private const int OfficialMotionVectorFixtureHeight = 288;
/// <summary> /// <summary>
/// The number of shown frames in the official libaom motion-vector sequence. /// The number of shown frames in the official AV1 motion-vector sequence.
/// </summary> /// </summary>
private const int OfficialMotionVectorFixtureFrameCount = 4; private const int OfficialMotionVectorFixtureFrameCount = 4;
@ -421,7 +421,7 @@ public class Av1ReconstructionConformanceTests
/// and twelve-bit AV1 and AVIF content. /// and twelve-bit AV1 and AVIF content.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeDeblockingFixturesMatchCurrentLibaomReference() public void DecodeDeblockingMatchesReference()
{ {
ValidateFixture( ValidateFixture(
TestImages.Heif.Av1Deblocking8BitAvif, TestImages.Heif.Av1Deblocking8BitAvif,
@ -457,11 +457,11 @@ public class Av1ReconstructionConformanceTests
/// <summary> /// <summary>
/// Verifies active CDEF syntax, strength selection, unit traversal, subsampling, frame edges, and final native /// Verifies active CDEF syntax, strength selection, unit traversal, subsampling, frame edges, and final native
/// samples against scalar libaom for independently encoded eight-, ten-, and twelve-bit still-picture streams /// samples against the independent scalar reference for independently encoded eight-, ten-, and twelve-bit still-picture streams
/// under normal SIMD dispatch and with hardware intrinsics disabled. /// under normal SIMD dispatch and with hardware intrinsics disabled.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeWithActiveCdefMatchesCurrentLibaomReference() public void DecodeCdefNativeMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateActiveCdefFixtures, ReconstructionConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateActiveCdefFixtures, ReconstructionConfigurations);
/// <summary> /// <summary>
@ -476,7 +476,7 @@ public class Av1ReconstructionConformanceTests
[WithFile(TestImages.Heif.Av1Cdef8BitAvif, PixelTypes.Rgba32, 768, 512, HeifBitDepth.Bit8)] [WithFile(TestImages.Heif.Av1Cdef8BitAvif, PixelTypes.Rgba32, 768, 512, HeifBitDepth.Bit8)]
[WithFile(TestImages.Heif.Av1Cdef10BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit10)] [WithFile(TestImages.Heif.Av1Cdef10BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit10)]
[WithFile(TestImages.Heif.Av1Cdef12BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit12)] [WithFile(TestImages.Heif.Av1Cdef12BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit12)]
public void DecodeWithActiveCdefMatchesPinnedLibavifPresentation( public void DecodeCdefMatchesReference(
TestImageProvider<Rgba32> provider, TestImageProvider<Rgba32> provider,
int width, int width,
int height, int height,
@ -495,7 +495,7 @@ public class Av1ReconstructionConformanceTests
/// supported by AVIF across every available vector width and the scalar fallback. /// supported by AVIF across every available vector width and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeProfileMatrixMatchesPinnedLibaomReference() public void DecodeProfileMatrixNativeMatchesReference()
=> ValidateProfileNativeFixtures(); => ValidateProfileNativeFixtures();
/// <summary> /// <summary>
@ -503,7 +503,7 @@ public class Av1ReconstructionConformanceTests
/// under each narrower vector width and the scalar fallback. /// under each narrower vector width and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeProfileMatrixFallbacksMatchPinnedLibaomReference() public void DecodeProfileMatrixFallbacksMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateProfileNativeFixtures, ProfileFallbackConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateProfileNativeFixtures, ProfileFallbackConfigurations);
/// <summary> /// <summary>
@ -525,7 +525,7 @@ public class Av1ReconstructionConformanceTests
[WithFile(TestImages.Heif.Av1Profile12Bit420Avif, PixelTypes.Rgba32, HeifBitDepth.Bit12)] [WithFile(TestImages.Heif.Av1Profile12Bit420Avif, PixelTypes.Rgba32, HeifBitDepth.Bit12)]
[WithFile(TestImages.Heif.Av1Profile12Bit422Avif, PixelTypes.Rgba32, HeifBitDepth.Bit12)] [WithFile(TestImages.Heif.Av1Profile12Bit422Avif, PixelTypes.Rgba32, HeifBitDepth.Bit12)]
[WithFile(TestImages.Heif.Av1Profile12Bit444Avif, PixelTypes.Rgba32, HeifBitDepth.Bit12)] [WithFile(TestImages.Heif.Av1Profile12Bit444Avif, PixelTypes.Rgba32, HeifBitDepth.Bit12)]
public void DecodeProfileMatrixMatchesPinnedLibavifPresentation( public void DecodeProfileMatrixMatchesReference(
TestImageProvider<Rgba32> provider, TestImageProvider<Rgba32> provider,
HeifBitDepth bitDepth) HeifBitDepth bitDepth)
{ {
@ -547,15 +547,15 @@ public class Av1ReconstructionConformanceTests
} }
/// <summary> /// <summary>
/// Verifies decoded luma and chroma palette syntax and exact native samples against current official libaom for an /// Verifies decoded luma and chroma palette syntax and exact native samples for an independently encoded AV1
/// independently encoded AV1 still-picture stream. /// still-picture stream.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeWithPaletteMatchesCurrentLibaomReference() public void DecodePaletteMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePaletteNativeFixture, PaletteConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePaletteNativeFixture, PaletteConfigurations);
/// <summary> /// <summary>
/// Verifies exact palette reconstruction through segmented frame-owned map storage and tracked disposal. /// Verifies exact palette reconstruction through bounded reusable superblock maps and tracked disposal.
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
@ -567,40 +567,19 @@ public class Av1ReconstructionConformanceTests
configuration.MemoryAllocator = allocator; configuration.MemoryAllocator = allocator;
byte[] payload = TestFile.Create(TestImages.Heif.Av1Palette8BitPayload).Bytes; byte[] payload = TestFile.Create(TestImages.Heif.Av1Palette8BitPayload).Bytes;
byte[] reference = TestFile.Create(TestImages.Heif.Av1Palette8BitReference).Bytes; byte[] reference = TestFile.Create(TestImages.Heif.Av1Palette8BitReference).Bytes;
bool foundSegmentedLumaMap = false;
bool foundSegmentedChromaMap = false;
using (Av1Decoder decoder = new(configuration)) using (Av1Decoder decoder = new(configuration))
{ {
int paletteMapLength = 1 << Av1Constants.MaxSuperBlockSizeLog2;
int expectedPaletteAllocationCount = (2 * paletteMapLength * paletteMapLength) / allocator.BufferCapacityInBytes;
Assert.Equal(expectedPaletteAllocationCount, allocator.AllocationLog.Count);
int finalPaletteAllocationId = allocator.AllocationLog[^1].AllocationId;
using Av1FrameBuffer<byte> frameBuffer = decoder.DecodeFrameBuffer(payload, null, null, out _); using Av1FrameBuffer<byte> frameBuffer = decoder.DecodeFrameBuffer(payload, null, null, out _);
Assert.Equal(RequiredPaletteCoverage, GetPaletteCoverage(decoder)); Assert.Equal(RequiredPaletteCoverage, GetPaletteCoverage(decoder));
AssertNativePlanesEqual(decoder, frameBuffer, reference); AssertNativePlanesEqual(decoder, frameBuffer, reference);
Assert.NotNull(decoder.FrameHeader); Assert.DoesNotContain(allocator.ReturnLog, returned => returned.AllocationId <= finalPaletteAllocationId);
Assert.NotNull(decoder.FrameInfo);
int modeInfoWidth = Av1Math.DivideLog2Ceiling(decoder.FrameHeader.FrameSize.FrameWidth, Av1Constants.ModeInfoSizeLog2);
int modeInfoHeight = Av1Math.DivideLog2Ceiling(decoder.FrameHeader.FrameSize.FrameHeight, Av1Constants.ModeInfoSizeLog2);
for (int y = 0; y < modeInfoHeight && (!foundSegmentedLumaMap || !foundSegmentedChromaMap); y++)
{
for (int x = 0; x < modeInfoWidth && (!foundSegmentedLumaMap || !foundSegmentedChromaMap); x++)
{
Av1BlockModeInfo modeInfo = decoder.FrameInfo.GetModeInfoAt(new Point(x, y));
if (!foundSegmentedLumaMap && modeInfo.GetPaletteSize(Av1PlaneType.Y) != 0)
{
Buffer2DRegion<byte> map = modeInfo.GetPaletteColorIndexMap(Av1Plane.Y);
foundSegmentedLumaMap = map.Buffer.MemoryGroup.Count > 1;
}
if (!foundSegmentedChromaMap && modeInfo.GetPaletteSize(Av1PlaneType.Uv) != 0)
{
Buffer2DRegion<byte> map = modeInfo.GetPaletteColorIndexMap(Av1Plane.U);
foundSegmentedChromaMap = map.Buffer.MemoryGroup.Count > 1;
}
}
}
Assert.True(foundSegmentedLumaMap);
Assert.True(foundSegmentedChromaMap);
} }
Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count);
@ -670,7 +649,7 @@ public class Av1ReconstructionConformanceTests
/// <param name="provider">The AVIF input and matching reference-output naming context.</param> /// <param name="provider">The AVIF input and matching reference-output naming context.</param>
[Theory] [Theory]
[WithFile(TestImages.Heif.Av1Palette8BitAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1Palette8BitAvif, PixelTypes.Rgba32)]
public void DecodeWithPaletteMatchesRetainedPresentationReference(TestImageProvider<Rgba32> provider) public void DecodePalettePresentationMatchesReference(TestImageProvider<Rgba32> provider)
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidatePresentedFixture, ValidatePresentedFixture,
PresentationConfigurations, PresentationConfigurations,
@ -713,11 +692,11 @@ public class Av1ReconstructionConformanceTests
} }
/// <summary> /// <summary>
/// Verifies selected intra-block-copy prediction and exact native samples against scalar libaom for an /// Verifies selected intra-block-copy prediction and exact native samples against the independent scalar reference for an
/// independently encoded AV1 still pictures across every available vector width and the scalar fallback. /// independently encoded AV1 still pictures across every available vector width and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeWithIntraBlockCopyMatchesPinnedLibaomReference() public void DecodeIntraBlockCopyNativeMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateIntraBlockCopyNativeFixtures, IntraBlockCopyConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateIntraBlockCopyNativeFixtures, IntraBlockCopyConfigurations);
/// <summary> /// <summary>
@ -729,7 +708,7 @@ public class Av1ReconstructionConformanceTests
[WithFile(TestImages.Heif.Av1IntraBlockCopy8BitAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1IntraBlockCopy8BitAvif, PixelTypes.Rgba32)]
[WithFile(TestImages.Heif.Av1IntraBlockCopy10BitAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1IntraBlockCopy10BitAvif, PixelTypes.Rgba32)]
[WithFile(TestImages.Heif.Av1IntraBlockCopy12BitAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1IntraBlockCopy12BitAvif, PixelTypes.Rgba32)]
public void DecodeWithIntraBlockCopyMatchesPinnedLibavifPresentation( public void DecodeIntraBlockCopyMatchesReference(
TestImageProvider<Rgba32> provider) TestImageProvider<Rgba32> provider)
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidatePresentedFixture, ValidatePresentedFixture,
@ -743,7 +722,7 @@ public class Av1ReconstructionConformanceTests
/// <param name="provider">The AVIF input and matching reference-output naming context.</param> /// <param name="provider">The AVIF input and matching reference-output naming context.</param>
[Theory] [Theory]
[WithFile(TestImages.Heif.Av1Progressive8BitAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1Progressive8BitAvif, PixelTypes.Rgba32)]
public void DecodeProgressiveSingleReferenceMatchesCurrentLibaomReferences( public void DecodeProgressiveSingleMatchesReference(
TestImageProvider<Rgba32> provider) TestImageProvider<Rgba32> provider)
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateProgressiveSingleReferenceFixtureWithDefaultConfiguration, ValidateProgressiveSingleReferenceFixtureWithDefaultConfiguration,
@ -755,7 +734,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeProgressiveSingleReferenceWithConstrainedAllocator() public void DecodeProgressiveSingleWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 1_024 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 1_024 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -774,12 +753,12 @@ public class Av1ReconstructionConformanceTests
/// <summary> /// <summary>
/// Verifies that an essential lsel property returns the selected base spatial layer rather than the final /// Verifies that an essential lsel property returns the selected base spatial layer rather than the final
/// progressive layer, with exact current-libaom native planes and the retained exact presentation reference. /// progressive layer, with exact reference native planes and the retained exact presentation reference.
/// </summary> /// </summary>
/// <param name="provider">The selected-layer AVIF input and matching reference-output naming context.</param> /// <param name="provider">The selected-layer AVIF input and matching reference-output naming context.</param>
[Theory] [Theory]
[WithFile(TestImages.Heif.Av1ScaledReferenceSelectedLayerAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1ScaledReferenceSelectedLayerAvif, PixelTypes.Rgba32)]
public void DecodeSelectedProgressiveSpatialLayerMatchesCurrentLibaomReferences( public void DecodeSelectedProgressiveLayerMatchesReference(
TestImageProvider<Rgba32> provider) TestImageProvider<Rgba32> provider)
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateSelectedProgressiveSpatialLayerWithDefaultConfiguration, ValidateSelectedProgressiveSpatialLayerWithDefaultConfiguration,
@ -791,7 +770,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeSelectedProgressiveSpatialLayerWithConstrainedAllocator() public void DecodeSelectedProgressiveLayerWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 1_024 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 1_024 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -810,12 +789,12 @@ public class Av1ReconstructionConformanceTests
/// <summary> /// <summary>
/// Verifies an independently encoded 40x40 retained layer scaled into an 80x80 dependent layer against exact /// Verifies an independently encoded 40x40 retained layer scaled into an 80x80 dependent layer against exact
/// current-libaom native planes and the retained exact presentation reference. /// reference native planes and the retained exact presentation reference.
/// </summary> /// </summary>
/// <param name="provider">The AVIF input and matching reference-output naming context.</param> /// <param name="provider">The AVIF input and matching reference-output naming context.</param>
[Theory] [Theory]
[WithFile(TestImages.Heif.Av1ScaledReferenceAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1ScaledReferenceAvif, PixelTypes.Rgba32)]
public void DecodeScaledReferenceMatchesCurrentLibaomReferences(TestImageProvider<Rgba32> provider) public void DecodeScaledReferenceMatchesReference(TestImageProvider<Rgba32> provider)
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateScaledReferenceFixtureWithDefaultConfiguration, ValidateScaledReferenceFixtureWithDefaultConfiguration,
ReconstructionConfigurations, ReconstructionConfigurations,
@ -849,7 +828,7 @@ public class Av1ReconstructionConformanceTests
/// Verifies that the production dependent-frame result owns its motion-field storage until decoder disposal. /// Verifies that the production dependent-frame result owns its motion-field storage until decoder disposal.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeProgressiveSingleReferenceTracksMotionFieldResultOwnership() public void DecodeProgressiveSingleTracksMotionFieldOwnership()
{ {
TestMemoryAllocator allocator = new(); TestMemoryAllocator allocator = new();
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -905,11 +884,11 @@ public class Av1ReconstructionConformanceTests
/// <summary> /// <summary>
/// Verifies exact native reconstruction and presentation for an image sequence that exercises equal-weight /// Verifies exact native reconstruction and presentation for an image sequence that exercises equal-weight
/// compound prediction. The native reference has been reverified against current official libaom main. /// compound prediction. The native reference has been reverified against the current AV1 reference.
/// </summary> /// </summary>
[Theory] [Theory]
[WithFile(TestImages.Heif.Av1AverageCompoundSequenceAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1AverageCompoundSequenceAvif, PixelTypes.Rgba32)]
public void DecodeRealLibavifSequenceWithEqualAverageCompoundMatchesVerifiedReferences( public void DecodeEqualAverageCompoundMatchesReference(
TestImageProvider<Rgba32> provider) TestImageProvider<Rgba32> provider)
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
@ -922,7 +901,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeRealLibavifSequenceWithEqualAverageCompoundUsesContiguousPlanes() public void DecodeEqualAverageCompoundWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 1_024 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 1_024 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -1066,7 +1045,7 @@ public class Av1ReconstructionConformanceTests
[WithFile(TestImages.Heif.Av1WedgeCompoundSequenceAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1WedgeCompoundSequenceAvif, PixelTypes.Rgba32)]
[WithFile(TestImages.Heif.Av1DifferenceWeightedCompoundSequenceAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1DifferenceWeightedCompoundSequenceAvif, PixelTypes.Rgba32)]
[WithFile(TestImages.Heif.Av1InterIntraSequenceAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1InterIntraSequenceAvif, PixelTypes.Rgba32)]
public void DecodeRealLibavifSequencesWithSelectableCompoundAndInterIntraMatchesRetainedReferences( public void DecodeSelectableCompoundMatchesReference(
TestImageProvider<Rgba32> provider) TestImageProvider<Rgba32> provider)
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
@ -1079,7 +1058,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeRealLibavifSequencesWithSelectableCompoundAndInterIntraUseContiguousPlanes() public void DecodeSelectableCompoundWithConstrainedAllocator()
{ {
ValidateInterPredictionSequenceWithConstrainedAllocator( ValidateInterPredictionSequenceWithConstrainedAllocator(
TestImages.Heif.Av1DistanceWeightedCompoundSequenceAvif, TestImages.Heif.Av1DistanceWeightedCompoundSequenceAvif,
@ -1112,11 +1091,11 @@ public class Av1ReconstructionConformanceTests
/// <summary> /// <summary>
/// Verifies production OBMC reconstruction against exact native and presentation references. The native reference /// Verifies production OBMC reconstruction against exact native and presentation references. The native reference
/// has been reverified against current official libaom main. /// has been reverified against the current AV1 reference.
/// </summary> /// </summary>
[Theory] [Theory]
[WithFile(TestImages.Heif.Av1ObmcSequenceAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1ObmcSequenceAvif, PixelTypes.Rgba32)]
public void DecodeRealLibavifObmcSequenceMatchesVerifiedReferences(TestImageProvider<Rgba32> provider) public void DecodeObmcMatchesReference(TestImageProvider<Rgba32> provider)
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateObmcSequenceWithDefaultConfiguration, ValidateObmcSequenceWithDefaultConfiguration,
ReconstructionConfigurations, ReconstructionConfigurations,
@ -1127,7 +1106,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeRealLibavifObmcSequenceUsesContiguousPlanes() public void DecodeObmcWithConstrainedAllocator()
=> ValidateInterPredictionSequenceWithConstrainedAllocator( => ValidateInterPredictionSequenceWithConstrainedAllocator(
TestImages.Heif.Av1ObmcSequenceAvif, TestImages.Heif.Av1ObmcSequenceAvif,
TestImages.Heif.Av1ObmcSequenceNativeReference, TestImages.Heif.Av1ObmcSequenceNativeReference,
@ -1136,11 +1115,11 @@ public class Av1ReconstructionConformanceTests
AverageCompoundFixtureFrameCount); AverageCompoundFixtureFrameCount);
/// <summary> /// <summary>
/// Verifies production local warped-motion reconstruction against current-libaom native and retained presentation references. /// Verifies production local warped-motion reconstruction against reference native and retained presentation references.
/// </summary> /// </summary>
[Theory] [Theory]
[WithFile(TestImages.Heif.Av1LocalWarpSequenceAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1LocalWarpSequenceAvif, PixelTypes.Rgba32)]
public void DecodeRealLibavifLocalWarpSequenceMatchesCurrentLibaomReferences(TestImageProvider<Rgba32> provider) public void DecodeLocalWarpMatchesReference(TestImageProvider<Rgba32> provider)
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateLocalWarpSequenceWithDefaultConfiguration, ValidateLocalWarpSequenceWithDefaultConfiguration,
ReconstructionConfigurations, ReconstructionConfigurations,
@ -1151,7 +1130,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeRealLibavifLocalWarpSequenceUsesContiguousPlanes() public void DecodeLocalWarpWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 1_024 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 1_024 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -1177,11 +1156,11 @@ public class Av1ReconstructionConformanceTests
} }
/// <summary> /// <summary>
/// Verifies production non-translational global motion against current-libaom native and retained presentation references. /// Verifies production non-translational global motion against reference native and retained presentation references.
/// </summary> /// </summary>
[Theory] [Theory]
[WithFile(TestImages.Heif.Av1GlobalWarpSequenceAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.Av1GlobalWarpSequenceAvif, PixelTypes.Rgba32)]
public void DecodeRealLibavifGlobalWarpSequenceMatchesCurrentLibaomReferences(TestImageProvider<Rgba32> provider) public void DecodeGlobalWarpMatchesReference(TestImageProvider<Rgba32> provider)
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateGlobalWarpSequenceWithDefaultConfiguration, ValidateGlobalWarpSequenceWithDefaultConfiguration,
ReconstructionConfigurations, ReconstructionConfigurations,
@ -1192,7 +1171,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeRealLibavifGlobalWarpSequenceUsesContiguousPlanes() public void DecodeGlobalWarpWithConstrainedAllocator()
=> ValidateInterPredictionSequenceWithConstrainedAllocator( => ValidateInterPredictionSequenceWithConstrainedAllocator(
TestImages.Heif.Av1GlobalWarpSequenceAvif, TestImages.Heif.Av1GlobalWarpSequenceAvif,
TestImages.Heif.Av1GlobalWarpSequenceNativeReference, TestImages.Heif.Av1GlobalWarpSequenceNativeReference,
@ -1202,10 +1181,10 @@ public class Av1ReconstructionConformanceTests
/// <summary> /// <summary>
/// Verifies every intra prediction mode and the fixture's seven transform types against the official /// Verifies every intra prediction mode and the fixture's seven transform types against the official
/// current-libaom all-intra conformance sequence and its exact native output. /// reference all-intra conformance sequence and its exact native output.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialAllIntraSequenceMatchesCurrentLibaomReference() => ValidateOfficialAllIntraFixture(); public void DecodeAllIntraMatchesReference() => ValidateOfficialAllIntraFixture();
/// <summary> /// <summary>
/// Decodes every all-intra IVF sample in one session, compares each frame exactly, and records the syntax /// Decodes every all-intra IVF sample in one session, compares each frame exactly, and records the syntax
@ -1304,31 +1283,31 @@ public class Av1ReconstructionConformanceTests
} }
/// <summary> /// <summary>
/// Verifies adaptive tile and frame-end CDF updates against the official pinned-libaom sequence and exact native /// Verifies adaptive tile and frame-end CDF updates against the official reference sequence and exact native
/// output under normal and scalar dispatch. /// output under normal and scalar dispatch.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialCdfUpdateSequenceMatchesPinnedLibaomReference() public void DecodeCdfUpdateMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialCdfUpdateFixture, ValidateOfficialCdfUpdateFixture,
ReconstructionConfigurations); ReconstructionConfigurations);
/// <summary> /// <summary>
/// Verifies temporal reference-motion-vector projection against the official pinned-libaom sequence and exact /// Verifies temporal reference-motion-vector projection against the official reference sequence and exact
/// native output under normal and scalar dispatch. /// native output under normal and scalar dispatch.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialMotionFieldSequenceMatchesPinnedLibaomReference() public void DecodeMotionFieldMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialMotionFieldFixture, ValidateOfficialMotionFieldFixture,
ReconstructionConfigurations); ReconstructionConfigurations);
/// <summary> /// <summary>
/// Verifies extreme intra-block-copy displacement vectors against the official pinned-libaom sequence and exact /// Verifies extreme intra-block-copy displacement vectors against the official reference sequence and exact
/// native output under normal and scalar dispatch. /// native output under normal and scalar dispatch.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialIntraBlockCopySequenceMatchesPinnedLibaomReference() public void DecodeIntraBlockCopySequenceMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialIntraBlockCopyFixture, ValidateOfficialIntraBlockCopyFixture,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -1406,7 +1385,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeOfficialMotionFieldSequenceWithConstrainedAllocator() public void DecodeMotionFieldWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -1459,11 +1438,11 @@ public class Av1ReconstructionConformanceTests
} }
/// <summary> /// <summary>
/// Verifies the default operating point of an official two-spatial-layer sequence against exact pinned-libaom /// Verifies the default operating point of an official two-spatial-layer sequence against exact reference
/// native output under normal and scalar dispatch. /// native output under normal and scalar dispatch.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialTwoSpatialLayerSequenceMatchesPinnedLibaomReference() public void DecodeTwoSpatialLayersMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialTwoSpatialLayerFixture, ValidateOfficialTwoSpatialLayerFixture,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -1473,7 +1452,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeOfficialTwoSpatialLayerSequenceWithConstrainedAllocator() public void DecodeTwoSpatialLayersWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 8_192 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 8_192 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -1509,11 +1488,11 @@ public class Av1ReconstructionConformanceTests
OfficialTwoSpatialLayerFixtureHeight); OfficialTwoSpatialLayerFixtureHeight);
/// <summary> /// <summary>
/// Verifies the default operating point of an official two-temporal-layer sequence against exact pinned-libaom /// Verifies the default operating point of an official two-temporal-layer sequence against exact reference
/// native output under normal and scalar dispatch. /// native output under normal and scalar dispatch.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialTwoTemporalLayerSequenceMatchesPinnedLibaomReference() public void DecodeTwoTemporalLayersMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialTwoTemporalLayerFixture, ValidateOfficialTwoTemporalLayerFixture,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -1523,7 +1502,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeOfficialTwoTemporalLayerSequenceWithConstrainedAllocator() public void DecodeTwoTemporalLayersWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 8_192 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 8_192 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -1560,10 +1539,10 @@ public class Av1ReconstructionConformanceTests
/// <summary> /// <summary>
/// Verifies the default operating point of an official spatial-and-temporal-layer sequence against exact /// Verifies the default operating point of an official spatial-and-temporal-layer sequence against exact
/// pinned-libaom native output under normal and scalar dispatch. /// reference native output under normal and scalar dispatch.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialSpatialTemporalLayerSequenceMatchesPinnedLibaomReference() public void DecodeSpatialTemporalLayersMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialSpatialTemporalLayerFixture, ValidateOfficialSpatialTemporalLayerFixture,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -1573,7 +1552,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeOfficialSpatialTemporalLayerSequenceWithConstrainedAllocator() public void DecodeSpatialTemporalLayersWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 8_192 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 8_192 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -1609,11 +1588,11 @@ public class Av1ReconstructionConformanceTests
OfficialSpatialTemporalLayerFixtureHeight); OfficialSpatialTemporalLayerFixtureHeight);
/// <summary> /// <summary>
/// Verifies active film-grain presentation and dependent-frame reconstruction against exact pinned-libaom native /// Verifies active film-grain presentation and dependent-frame reconstruction against exact reference native
/// output under normal and scalar dispatch. /// output under normal and scalar dispatch.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialFilmGrainSequenceMatchesPinnedLibaomReference() public void DecodeFilmGrainSequenceMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialFilmGrainFixture, ValidateOfficialFilmGrainFixture,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -1623,7 +1602,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeOfficialFilmGrainSequenceWithConstrainedAllocator() public void DecodeFilmGrainWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -1660,10 +1639,10 @@ public class Av1ReconstructionConformanceTests
} }
/// <summary> /// <summary>
/// Verifies the official ten-bit film-grain sequence against exact pinned-libaom native output. /// Verifies the official ten-bit film-grain sequence against exact reference native output.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialTenBitFilmGrainSequenceMatchesPinnedLibaomReference() public void DecodeTenBitFilmGrainSequenceMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialTenBitFilmGrainFixture, ValidateOfficialTenBitFilmGrainFixture,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -1687,11 +1666,11 @@ public class Av1ReconstructionConformanceTests
} }
/// <summary> /// <summary>
/// Verifies the official eight-bit monochrome sequence against exact pinned-libaom native output under normal and /// Verifies the official eight-bit monochrome sequence against exact reference native output under normal and
/// scalar dispatch. /// scalar dispatch.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialMonochromeSequenceMatchesPinnedLibaomReference() public void DecodeMonochromeSequenceMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialMonochromeFixture, ValidateOfficialMonochromeFixture,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -1701,7 +1680,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeOfficialMonochromeSequenceWithConstrainedAllocator() public void DecodeMonochromeWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -1739,10 +1718,10 @@ public class Av1ReconstructionConformanceTests
Av1ColorFormat.Yuv400); Av1ColorFormat.Yuv400);
/// <summary> /// <summary>
/// Verifies the official ten-bit monochrome sequence against exact pinned-libaom native output. /// Verifies the official ten-bit monochrome sequence against exact reference native output.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialTenBitMonochromeSequenceMatchesPinnedLibaomReference() public void DecodeTenBitMonochromeSequenceMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialTenBitMonochromeFixture, ValidateOfficialTenBitMonochromeFixture,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -1766,7 +1745,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeOfficialTenBitSequencesWithConstrainedAllocator() public void DecodeTenBitWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -1803,10 +1782,10 @@ public class Av1ReconstructionConformanceTests
} }
/// <summary> /// <summary>
/// Verifies the official eight-bit quantizer boundaries against exact current-libaom native output. /// Verifies the official eight-bit quantizer boundaries against exact reference native output.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialEightBitQuantizerBoundarySequencesMatchCurrentLibaomReferences() public void DecodeEightBitQuantizerBoundariesMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialEightBitQuantizerBoundaryFixtures, ValidateOfficialEightBitQuantizerBoundaryFixtures,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -1818,10 +1797,10 @@ public class Av1ReconstructionConformanceTests
=> ValidateOfficialEightBitQuantizerBoundaryFixturesWithConfiguration(Configuration.Default); => ValidateOfficialEightBitQuantizerBoundaryFixturesWithConfiguration(Configuration.Default);
/// <summary> /// <summary>
/// Verifies the official ten-bit quantizer boundaries against exact current-libaom native output. /// Verifies the official ten-bit quantizer boundaries against exact reference native output.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialTenBitQuantizerBoundarySequencesMatchCurrentLibaomReferences() public void DecodeTenBitQuantizerBoundariesMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialTenBitQuantizerBoundaryFixtures, ValidateOfficialTenBitQuantizerBoundaryFixtures,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -1837,7 +1816,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeOfficialQuantizerBoundarySequencesWithConstrainedAllocator() public void DecodeQuantizerBoundariesWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_560 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_560 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -1916,10 +1895,10 @@ public class Av1ReconstructionConformanceTests
} }
/// <summary> /// <summary>
/// Verifies all four corners of the official frame-size matrix against exact pinned-libaom native output. /// Verifies all four corners of the official frame-size matrix against exact reference native output.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialFrameSizeCornerSequencesMatchPinnedLibaomReferences() public void DecodeFrameSizeCornersMatchReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialFrameSizeCornerFixtures, ValidateOfficialFrameSizeCornerFixtures,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -1935,7 +1914,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeOfficialFrameSizeCornerSequencesWithConstrainedAllocator() public void DecodeFrameSizeCornersWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 1_024 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 1_024 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -2100,10 +2079,10 @@ public class Av1ReconstructionConformanceTests
/// <summary> /// <summary>
/// Verifies every ordinary inter mode, motion mode, and switchable dual-filter pair against the official /// Verifies every ordinary inter mode, motion mode, and switchable dual-filter pair against the official
/// pinned-libaom motion-vector conformance sequence and its exact native output. /// reference motion-vector conformance sequence and its exact native output.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeOfficialMotionVectorSequenceMatchesPinnedLibaomReference() public void DecodeMotionVectorSequenceMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateOfficialMotionVectorFixtureWithDefaultConfiguration, ValidateOfficialMotionVectorFixtureWithDefaultConfiguration,
ReconstructionConfigurations); ReconstructionConfigurations);
@ -2113,7 +2092,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
[Fact] [Fact]
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public void DecodeOfficialMotionVectorSequenceUsesContiguousPlanes() public void DecodeMotionVectorsWithConstrainedAllocator()
{ {
TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 }; TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 };
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();
@ -2509,11 +2488,11 @@ public class Av1ReconstructionConformanceTests
} }
/// <summary> /// <summary>
/// Verifies lossless syntax, residual reconstruction, and exact native samples against scalar libaom for /// Verifies lossless syntax, residual reconstruction, and exact native samples against the independent scalar reference for
/// independently encoded eight-, ten-, and twelve-bit AVIF images. /// independently encoded eight-, ten-, and twelve-bit AVIF images.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeLosslessMatchesPinnedLibaomReference() public void DecodeLosslessNativeMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateLosslessFixtures, LosslessConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateLosslessFixtures, LosslessConfigurations);
/// <summary> /// <summary>
@ -2526,7 +2505,7 @@ public class Av1ReconstructionConformanceTests
[WithFile(TestImages.Heif.Av1Lossless8BitAvif, PixelTypes.Rgba32, HeifBitDepth.Bit8)] [WithFile(TestImages.Heif.Av1Lossless8BitAvif, PixelTypes.Rgba32, HeifBitDepth.Bit8)]
[WithFile(TestImages.Heif.Av1Lossless10BitAvif, PixelTypes.Rgba32, HeifBitDepth.Bit10)] [WithFile(TestImages.Heif.Av1Lossless10BitAvif, PixelTypes.Rgba32, HeifBitDepth.Bit10)]
[WithFile(TestImages.Heif.Av1Lossless12BitAvif, PixelTypes.Rgba32, HeifBitDepth.Bit12)] [WithFile(TestImages.Heif.Av1Lossless12BitAvif, PixelTypes.Rgba32, HeifBitDepth.Bit12)]
public void DecodeLosslessMatchesPinnedLibavifPresentation( public void DecodeLosslessMatchesReference(
TestImageProvider<Rgba32> provider, TestImageProvider<Rgba32> provider,
HeifBitDepth bitDepth) HeifBitDepth bitDepth)
{ {
@ -2540,10 +2519,10 @@ public class Av1ReconstructionConformanceTests
/// <summary> /// <summary>
/// Verifies active normative super-resolution, chroma-width rounding, replicated edges, and exact native samples /// Verifies active normative super-resolution, chroma-width rounding, replicated edges, and exact native samples
/// against scalar libaom for independently encoded eight-, ten-, and twelve-bit still-picture streams. /// against the independent scalar reference for independently encoded eight-, ten-, and twelve-bit still-picture streams.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeWithSuperResolutionMatchesPinnedLibaomReference() public void DecodeSuperResolutionNativeMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateSuperResolutionFixtures, ReconstructionConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateSuperResolutionFixtures, ReconstructionConfigurations);
/// <summary> /// <summary>
@ -2558,7 +2537,7 @@ public class Av1ReconstructionConformanceTests
[WithFile(TestImages.Heif.Av1SuperResolution8BitAvif, PixelTypes.Rgba32, 768, 512, HeifBitDepth.Bit8)] [WithFile(TestImages.Heif.Av1SuperResolution8BitAvif, PixelTypes.Rgba32, 768, 512, HeifBitDepth.Bit8)]
[WithFile(TestImages.Heif.Av1SuperResolution10BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit10)] [WithFile(TestImages.Heif.Av1SuperResolution10BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit10)]
[WithFile(TestImages.Heif.Av1SuperResolution12BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit12)] [WithFile(TestImages.Heif.Av1SuperResolution12BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit12)]
public void DecodeWithSuperResolutionMatchesPinnedLibavifPresentation( public void DecodeSuperResolutionMatchesReference(
TestImageProvider<Rgba32> provider, TestImageProvider<Rgba32> provider,
int width, int width,
int height, int height,
@ -2573,18 +2552,18 @@ public class Av1ReconstructionConformanceTests
} }
/// <summary> /// <summary>
/// Verifies active normative loop restoration and exact native samples against scalar libaom for independently /// Verifies active normative loop restoration and exact native samples against the independent scalar reference for independently
/// encoded eight-, ten-, and twelve-bit still-picture streams. /// encoded eight-, ten-, and twelve-bit still-picture streams.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeWithLoopRestorationMatchesPinnedLibaomReference() public void DecodeLoopRestorationNativeMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateLoopRestorationFixtures, LoopRestorationConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateLoopRestorationFixtures, LoopRestorationConfigurations);
/// <summary> /// <summary>
/// Verifies combined super-resolution and loop-restoration geometry for independently encoded 8-bit 4:2:0 content. /// Verifies combined super-resolution and loop-restoration geometry for independently encoded 8-bit 4:2:0 content.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeWithLoopRestorationAndSuperResolutionMatchesPinnedLibaomReference8Bit420() public void DecodeRestorationSuperResolution8Bit420MatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateLoopRestorationAndSuperResolution8Bit420, ValidateLoopRestorationAndSuperResolution8Bit420,
LoopRestorationConfigurations); LoopRestorationConfigurations);
@ -2593,7 +2572,7 @@ public class Av1ReconstructionConformanceTests
/// Verifies combined super-resolution and loop-restoration geometry for independently encoded 10-bit 4:2:2 content. /// Verifies combined super-resolution and loop-restoration geometry for independently encoded 10-bit 4:2:2 content.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeWithLoopRestorationAndSuperResolutionMatchesPinnedLibaomReference10Bit422() public void DecodeRestorationSuperRes10Bit422MatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateLoopRestorationAndSuperResolution10Bit422, ValidateLoopRestorationAndSuperResolution10Bit422,
LoopRestorationConfigurations); LoopRestorationConfigurations);
@ -2602,7 +2581,7 @@ public class Av1ReconstructionConformanceTests
/// Verifies combined super-resolution and loop-restoration geometry for independently encoded 12-bit 4:4:4 content. /// Verifies combined super-resolution and loop-restoration geometry for independently encoded 12-bit 4:4:4 content.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeWithLoopRestorationAndSuperResolutionMatchesPinnedLibaomReference12Bit444() public void DecodeRestorationSuperRes12Bit444MatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateLoopRestorationAndSuperResolution12Bit444, ValidateLoopRestorationAndSuperResolution12Bit444,
LoopRestorationConfigurations); LoopRestorationConfigurations);
@ -2619,7 +2598,7 @@ public class Av1ReconstructionConformanceTests
[WithFile(TestImages.Heif.Av1Restoration8BitAvif, PixelTypes.Rgba32, 768, 512, HeifBitDepth.Bit8)] [WithFile(TestImages.Heif.Av1Restoration8BitAvif, PixelTypes.Rgba32, 768, 512, HeifBitDepth.Bit8)]
[WithFile(TestImages.Heif.Av1Restoration10BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit10)] [WithFile(TestImages.Heif.Av1Restoration10BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit10)]
[WithFile(TestImages.Heif.Av1Restoration12BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit12)] [WithFile(TestImages.Heif.Av1Restoration12BitAvif, PixelTypes.Rgba32, 1024, 428, HeifBitDepth.Bit12)]
public void DecodeWithLoopRestorationMatchesPinnedLibavifPresentation( public void DecodeLoopRestorationMatchesReference(
TestImageProvider<Rgba32> provider, TestImageProvider<Rgba32> provider,
int width, int width,
int height, int height,
@ -2649,10 +2628,10 @@ public class Av1ReconstructionConformanceTests
/// <summary> /// <summary>
/// Verifies film-grain template generation, block selection, overlap, chroma scaling, subsampling, high-bit-depth /// Verifies film-grain template generation, block selection, overlap, chroma scaling, subsampling, high-bit-depth
/// arithmetic, and exact native presentation samples against scalar libaom. /// arithmetic, and exact native presentation samples against the independent scalar reference.
/// </summary> /// </summary>
[Fact] [Fact]
public void DecodeWithFilmGrainMatchesPinnedLibaomReference() public void DecodeFilmGrainMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateFilmGrainFixtures, LoopRestorationConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateFilmGrainFixtures, LoopRestorationConfigurations);
/// <summary> /// <summary>
@ -2763,7 +2742,7 @@ public class Av1ReconstructionConformanceTests
/// Validates one independently encoded AVIF against its native Y4M reference and signaled sequence profile. /// Validates one independently encoded AVIF against its native Y4M reference and signaled sequence profile.
/// </summary> /// </summary>
/// <param name="imagePath">The complete AVIF container.</param> /// <param name="imagePath">The complete AVIF container.</param>
/// <param name="referencePath">The native Y4M output produced by the pinned scalar libaom-backed decoder.</param> /// <param name="referencePath">The independently decoded native Y4M output.</param>
/// <param name="bitDepth">The expected AV1 sample precision.</param> /// <param name="bitDepth">The expected AV1 sample precision.</param>
/// <param name="colorFormat">The expected native chroma-sampling layout.</param> /// <param name="colorFormat">The expected native chroma-sampling layout.</param>
/// <param name="sequenceProfile">The AV1 profile required by the bit-depth and chroma-format combination.</param> /// <param name="sequenceProfile">The AV1 profile required by the bit-depth and chroma-format combination.</param>
@ -2912,7 +2891,7 @@ public class Av1ReconstructionConformanceTests
/// Validates one independently encoded intra-block-copy AVIF against its native Y4M reference. /// Validates one independently encoded intra-block-copy AVIF against its native Y4M reference.
/// </summary> /// </summary>
/// <param name="imagePath">The complete AVIF container.</param> /// <param name="imagePath">The complete AVIF container.</param>
/// <param name="referencePath">The native Y4M output produced by the pinned scalar libaom-backed decoder.</param> /// <param name="referencePath">The independently decoded native Y4M output.</param>
/// <param name="bitDepth">The expected AV1 sample precision.</param> /// <param name="bitDepth">The expected AV1 sample precision.</param>
private static void ValidateIntraBlockCopyNativeFixture(string imagePath, string referencePath, Av1BitDepth bitDepth) private static void ValidateIntraBlockCopyNativeFixture(string imagePath, string referencePath, Av1BitDepth bitDepth)
{ {
@ -3616,7 +3595,7 @@ public class Av1ReconstructionConformanceTests
/// Validates lossless frame syntax and complete native reconstruction for one AVIF image. /// Validates lossless frame syntax and complete native reconstruction for one AVIF image.
/// </summary> /// </summary>
/// <param name="imagePath">The independently encoded AVIF container.</param> /// <param name="imagePath">The independently encoded AVIF container.</param>
/// <param name="referencePath">The raw planar output produced by the pinned scalar libaom decoder.</param> /// <param name="referencePath">The independently decoded raw planar output.</param>
/// <param name="bitDepth">The expected AV1 sample precision.</param> /// <param name="bitDepth">The expected AV1 sample precision.</param>
private static void ValidateLosslessFixture(string imagePath, string referencePath, Av1BitDepth bitDepth) private static void ValidateLosslessFixture(string imagePath, string referencePath, Av1BitDepth bitDepth)
{ {
@ -3688,7 +3667,7 @@ public class Av1ReconstructionConformanceTests
/// Validates one independently encoded stream that activates constrained directional enhancement filtering. /// Validates one independently encoded stream that activates constrained directional enhancement filtering.
/// </summary> /// </summary>
/// <param name="payloadPath">The AV1 elementary-stream sample.</param> /// <param name="payloadPath">The AV1 elementary-stream sample.</param>
/// <param name="referencePath">The native planar output produced by the pinned scalar libaom decoder.</param> /// <param name="referencePath">The independently decoded native planar output.</param>
/// <param name="width">The expected reconstructed width.</param> /// <param name="width">The expected reconstructed width.</param>
/// <param name="height">The expected reconstructed height.</param> /// <param name="height">The expected reconstructed height.</param>
/// <param name="bitDepth">The expected AV1 sample precision.</param> /// <param name="bitDepth">The expected AV1 sample precision.</param>
@ -3706,7 +3685,7 @@ public class Av1ReconstructionConformanceTests
/// Validates one independently encoded stream that activates normative super-resolution. /// Validates one independently encoded stream that activates normative super-resolution.
/// </summary> /// </summary>
/// <param name="payloadPath">The AV1 elementary-stream sample.</param> /// <param name="payloadPath">The AV1 elementary-stream sample.</param>
/// <param name="referencePath">The native planar output produced by the pinned scalar libaom decoder.</param> /// <param name="referencePath">The independently decoded native planar output.</param>
/// <param name="width">The expected upscaled width.</param> /// <param name="width">The expected upscaled width.</param>
/// <param name="height">The expected reconstructed height.</param> /// <param name="height">The expected reconstructed height.</param>
/// <param name="bitDepth">The expected AV1 sample precision.</param> /// <param name="bitDepth">The expected AV1 sample precision.</param>
@ -3732,7 +3711,7 @@ public class Av1ReconstructionConformanceTests
/// Validates one independently encoded stream that activates normative loop restoration. /// Validates one independently encoded stream that activates normative loop restoration.
/// </summary> /// </summary>
/// <param name="payloadPath">The AV1 elementary-stream sample.</param> /// <param name="payloadPath">The AV1 elementary-stream sample.</param>
/// <param name="referencePath">The native planar output produced by the pinned scalar libaom decoder.</param> /// <param name="referencePath">The independently decoded native planar output.</param>
/// <param name="width">The expected reconstructed width.</param> /// <param name="width">The expected reconstructed width.</param>
/// <param name="height">The expected reconstructed height.</param> /// <param name="height">The expected reconstructed height.</param>
/// <param name="bitDepth">The expected AV1 sample precision.</param> /// <param name="bitDepth">The expected AV1 sample precision.</param>
@ -3762,7 +3741,7 @@ public class Av1ReconstructionConformanceTests
/// Validates one independently encoded stream that applies film grain to the displayed samples. /// Validates one independently encoded stream that applies film grain to the displayed samples.
/// </summary> /// </summary>
/// <param name="payloadPath">The AV1 elementary-stream sample.</param> /// <param name="payloadPath">The AV1 elementary-stream sample.</param>
/// <param name="referencePath">The native planar output produced by the pinned scalar libaom decoder.</param> /// <param name="referencePath">The independently decoded native planar output.</param>
/// <param name="width">The expected displayed width.</param> /// <param name="width">The expected displayed width.</param>
/// <param name="height">The expected displayed height.</param> /// <param name="height">The expected displayed height.</param>
/// <param name="bitDepth">The expected AV1 sample precision.</param> /// <param name="bitDepth">The expected AV1 sample precision.</param>
@ -4104,7 +4083,7 @@ public class Av1ReconstructionConformanceTests
/// </summary> /// </summary>
/// <param name="decoder">The decoder state used to identify the coded block containing a mismatch.</param> /// <param name="decoder">The decoder state used to identify the coded block containing a mismatch.</param>
/// <param name="frameBuffer">The reconstructed AV1 component planes.</param> /// <param name="frameBuffer">The reconstructed AV1 component planes.</param>
/// <param name="reference">The planar Y, U, and V samples produced by the current-main libaom decoder.</param> /// <param name="reference">The independently decoded planar Y, U, and V samples.</param>
/// <param name="frameIndex">The zero-based sequence-frame index, or -1 for a standalone sample.</param> /// <param name="frameIndex">The zero-based sequence-frame index, or -1 for a standalone sample.</param>
private static void AssertNativePlanesEqual( private static void AssertNativePlanesEqual(
Av1Decoder decoder, Av1Decoder decoder,

2
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceFrameStoreTests.cs

@ -207,7 +207,7 @@ public class Av1ReferenceFrameStoreTests
/// Verifies motion-field ownership across frame initialization, reference aliases, shown output, and final disposal. /// Verifies motion-field ownership across frame initialization, reference aliases, shown output, and final disposal.
/// </summary> /// </summary>
[Fact] [Fact]
public void MotionFieldsFollowReferenceAliasesAndPresentationOwnership() public void MotionFieldsFollowAliasesAndPresentationOwnership()
{ {
TestMemoryAllocator allocator = new(); TestMemoryAllocator allocator = new();
allocator.EnableNonThreadSafeLogging(); allocator.EnableNonThreadSafeLogging();

4
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
internal class Av1ReferenceTransform internal class Av1ReferenceTransform
{ {
/// <summary> /// <summary>
/// Gets the analytical amplification used by current libaom's forward-transform tests. /// Gets the analytical amplification used by the reference decoder's forward-transform tests.
/// </summary> /// </summary>
/// <param name="config">The transform configuration.</param> /// <param name="config">The transform configuration.</param>
/// <returns>The two-dimensional transform amplification.</returns> /// <returns>The two-dimensional transform amplification.</returns>
@ -32,7 +32,7 @@ internal class Av1ReferenceTransform
} }
/// <summary> /// <summary>
/// Applies the analytical two-dimensional transform used by current libaom's /// Applies the analytical two-dimensional transform used by the reference decoder's
/// <c>test/av1_txfm_test.cc</c>. /// <c>test/av1_txfm_test.cc</c>.
/// </summary> /// </summary>
/// <param name="input">The raster input samples.</param> /// <param name="input">The raster input samples.</param>

20
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScaledInterPredictorTests.cs

@ -7,7 +7,7 @@ using SixLabors.ImageSharp.Tests.TestUtilities;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
/// <summary> /// <summary>
/// Verifies AV1 reference scaling and variable-phase inter convolution against current libaom arithmetic. /// Verifies AV1 reference scaling and variable-phase inter convolution against the reference decoder arithmetic.
/// </summary> /// </summary>
[Trait("Format", "Avif")] [Trait("Format", "Avif")]
public class Av1ScaledInterPredictorTests public class Av1ScaledInterPredictorTests
@ -68,10 +68,10 @@ public class Av1ScaledInterPredictorTests
private const HwIntrinsics PredictorConfigurations = HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic; private const HwIntrinsics PredictorConfigurations = HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic;
/// <summary> /// <summary>
/// Verifies current libaom's Q14 scale factors, Q10 steps, and signed coordinate rounding. /// Verifies the reference decoder's Q14 scale factors, Q10 steps, and signed coordinate rounding.
/// </summary> /// </summary>
[Fact] [Fact]
public void ReferenceScaleMatchesCurrentLibaomFixedPointRules() public void ReferenceScaleMatchesFixedPointRules()
{ {
Av1ReferenceScale downscaledReference = new(40, 24, 64, 48); Av1ReferenceScale downscaledReference = new(40, 24, 64, 48);
@ -101,28 +101,28 @@ public class Av1ScaledInterPredictorTests
/// Verifies exact scaled 8-bit output, variable filter phases, vector tails, and untouched destination padding. /// Verifies exact scaled 8-bit output, variable filter phases, vector tails, and untouched destination padding.
/// </summary> /// </summary>
[Fact] [Fact]
public void BytePredictionMatchesCurrentLibaomOracleAcrossIntrinsicConfigurations() public void BytePredictionMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateBytePredictions, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateBytePredictions, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies exact scaled 8-, 10-, and 12-bit output under the native vector and scalar configurations. /// Verifies exact scaled 8-, 10-, and 12-bit output under the native vector and scalar configurations.
/// </summary> /// </summary>
[Fact] [Fact]
public void HighBitDepthPredictionMatchesCurrentLibaomOracleAcrossIntrinsicConfigurations() public void HighBitDepthPredictionMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthPredictions, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthPredictions, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies exact scaled 8-bit no-round compound intermediates under native vector and scalar configurations. /// Verifies exact scaled 8-bit no-round compound intermediates under native vector and scalar configurations.
/// </summary> /// </summary>
[Fact] [Fact]
public void ByteCompoundPredictionMatchesCurrentLibaomOracleAcrossIntrinsicConfigurations() public void ByteCompoundPredictionMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateByteCompoundPredictions, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateByteCompoundPredictions, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies exact scaled 8-, 10-, and 12-bit no-round compound intermediates under native vector and scalar configurations. /// Verifies exact scaled 8-, 10-, and 12-bit no-round compound intermediates under native vector and scalar configurations.
/// </summary> /// </summary>
[Fact] [Fact]
public void HighBitDepthCompoundPredictionMatchesCurrentLibaomOracleAcrossIntrinsicConfigurations() public void HighBitDepthCompoundPredictionMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthCompoundPredictions, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthCompoundPredictions, PredictorConfigurations);
/// <summary> /// <summary>
@ -677,7 +677,7 @@ public class Av1ScaledInterPredictorTests
} }
/// <summary> /// <summary>
/// Selects one current-libaom coefficient row without reading production filter storage. /// Selects one reference coefficient row without reading production filter storage.
/// </summary> /// </summary>
private static void FillCoefficients(Av1InterpolationFilter filter, int phase, bool reduced, Span<short> destination) private static void FillCoefficients(Av1InterpolationFilter filter, int phase, bool reduced, Span<short> destination)
{ {
@ -712,7 +712,7 @@ public class Av1ScaledInterPredictorTests
} }
/// <summary> /// <summary>
/// Computes libaom's bit-depth-dependent first-pass shift. /// Computes the reference decoder's bit-depth-dependent first-pass shift.
/// </summary> /// </summary>
private static int GetRound0Bits(int bitDepth) private static int GetRound0Bits(int bitDepth)
{ {
@ -726,7 +726,7 @@ public class Av1ScaledInterPredictorTests
private static int RoundPowerOfTwo(int value, int bits) => (value + (1 << (bits - 1))) >> bits; private static int RoundPowerOfTwo(int value, int bits) => (value + (1 << (bits - 1))) >> bits;
/// <summary> /// <summary>
/// Independently applies libaom's signed Q14-to-Q10 scale conversion. /// Independently applies the reference decoder's signed Q14-to-Q10 scale conversion.
/// </summary> /// </summary>
private static int ScaleCoordinate(int value, int scale) private static int ScaleCoordinate(int value, int scale)
{ {

4
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SelectableCompoundEntropyTests.cs

@ -16,10 +16,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
public class Av1SelectableCompoundEntropyTests public class Av1SelectableCompoundEntropyTests
{ {
/// <summary> /// <summary>
/// Verifies representative and complete multi-symbol defaults against pinned libaom's forward Q15 tables. /// Verifies representative and complete multi-symbol defaults against the reference decoder's forward Q15 tables.
/// </summary> /// </summary>
[Fact] [Fact]
public void DefaultsMatchPinnedLibaom() public void DefaultsMatchReference()
{ {
AssertForwardThresholds(Av1DefaultDistributions.InterIntraMode[1], [1875, 11082, 27332]); AssertForwardThresholds(Av1DefaultDistributions.InterIntraMode[1], [1875, 11082, 27332]);
AssertForwardThresholds(Av1DefaultDistributions.WedgeInterIntra[(int)Av1BlockSize.Block8x8], [20036]); AssertForwardThresholds(Av1DefaultDistributions.WedgeInterIntra[(int)Av1BlockSize.Block8x8], [20036]);

2
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SelfGuidedFilterTests.cs

@ -71,7 +71,7 @@ public class Av1SelfGuidedFilterTests
/// Verifies every normative parameter set, sample precision, and processing-unit tail against the direct-window definition. /// Verifies every normative parameter set, sample precision, and processing-unit tail against the direct-window definition.
/// </summary> /// </summary>
[Fact] [Fact]
public void FilterMatchesDirectWindowDefinitionAcrossIntrinsicWidths() public void FilterMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateFilters, Configurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateFilters, Configurations);
/// <summary> /// <summary>

22
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SingleReferenceEntropyTests.cs

@ -15,10 +15,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
public class Av1SingleReferenceEntropyTests public class Av1SingleReferenceEntropyTests
{ {
/// <summary> /// <summary>
/// Verifies all eighteen normative single-reference distributions against libaom's forward Q15 defaults. /// Verifies all eighteen normative single-reference distributions against the reference decoder's forward Q15 defaults.
/// </summary> /// </summary>
[Fact] [Fact]
public void SingleReferenceDefaultsMatchLibaom() public void SingleReferenceDefaultsMatchReference()
{ {
uint[][] forwardThresholds = uint[][] forwardThresholds =
[ [
@ -46,10 +46,10 @@ public class Av1SingleReferenceEntropyTests
} }
/// <summary> /// <summary>
/// Verifies the five normative block reference-mode distributions against libaom's forward Q15 defaults. /// Verifies the five normative block reference-mode distributions against the reference decoder's forward Q15 defaults.
/// </summary> /// </summary>
[Fact] [Fact]
public void CompInterDefaultsMatchLibaom() public void CompInterDefaultsMatchReference()
{ {
uint[] forwardThresholds = [26828, 24035, 12031, 10640, 2901]; uint[] forwardThresholds = [26828, 24035, 12031, 10640, 2901];
Av1Distribution[] distributions = Av1DefaultDistributions.CompInter; Av1Distribution[] distributions = Av1DefaultDistributions.CompInter;
@ -125,7 +125,7 @@ public class Av1SingleReferenceEntropyTests
/// Verifies one-pass neighbor collection, compound-neighbor votes, clearing, and intra-neighbor exclusion. /// Verifies one-pass neighbor collection, compound-neighbor votes, clearing, and intra-neighbor exclusion.
/// </summary> /// </summary>
[Fact] [Fact]
public void CollectNeighborReferenceCountsMatchesLibaom() public void CollectNeighborReferenceCountsMatchesReference()
{ {
Av1BlockModeInfo above = CreateModeInfo(Av1ReferenceFrameType.Last, Av1ReferenceFrameType.None); Av1BlockModeInfo above = CreateModeInfo(Av1ReferenceFrameType.Last, Av1ReferenceFrameType.None);
Av1BlockModeInfo left = CreateModeInfo(Av1ReferenceFrameType.Backward, Av1ReferenceFrameType.Alternate); Av1BlockModeInfo left = CreateModeInfo(Av1ReferenceFrameType.Backward, Av1ReferenceFrameType.Alternate);
@ -152,10 +152,10 @@ public class Av1SingleReferenceEntropyTests
} }
/// <summary> /// <summary>
/// Verifies that the six context functions aggregate the exact reference groups used by libaom. /// Verifies that the six context functions aggregate the exact reference groups used by the reference decoder.
/// </summary> /// </summary>
[Fact] [Fact]
public void SingleReferenceContextsAggregateNormativeReferenceGroups() public void ContextsAggregateReferenceGroups()
{ {
InlineArray8<byte> referenceCountStorage = default; InlineArray8<byte> referenceCountStorage = default;
Span<byte> referenceCounts = referenceCountStorage; Span<byte> referenceCounts = referenceCountStorage;
@ -176,10 +176,10 @@ public class Av1SingleReferenceEntropyTests
} }
/// <summary> /// <summary>
/// Verifies every branch of libaom's five-state single-versus-compound reference-mode context. /// Verifies every branch of the reference decoder's five-state single-versus-compound reference-mode context.
/// </summary> /// </summary>
[Fact] [Fact]
public void ReferenceModeContextMatchesLibaom() public void ReferenceModeContextMatchesReference()
{ {
Av1BlockModeInfo singleForward = CreateModeInfo(Av1ReferenceFrameType.Last, Av1ReferenceFrameType.None); Av1BlockModeInfo singleForward = CreateModeInfo(Av1ReferenceFrameType.Last, Av1ReferenceFrameType.None);
Av1BlockModeInfo singleBackward = CreateModeInfo(Av1ReferenceFrameType.Backward, Av1ReferenceFrameType.None); Av1BlockModeInfo singleBackward = CreateModeInfo(Av1ReferenceFrameType.Backward, Av1ReferenceFrameType.None);
@ -226,7 +226,7 @@ public class Av1SingleReferenceEntropyTests
/// Verifies that frame-context copies retain reference-selection adaptation without sharing mutable distributions. /// Verifies that frame-context copies retain reference-selection adaptation without sharing mutable distributions.
/// </summary> /// </summary>
[Fact] [Fact]
public void FrameEntropyCopyRetainsIndependentReferenceSelectionState() public void EntropyCopyRetainsReferenceSelectionState()
{ {
Av1FrameEntropyContext source = new(0); Av1FrameEntropyContext source = new(0);
Av1FrameEntropyContext destination = new(0); Av1FrameEntropyContext destination = new(0);
@ -249,7 +249,7 @@ public class Av1SingleReferenceEntropyTests
/// Verifies that publishing frame state resets the reference-selection distributions' update-rate history. /// Verifies that publishing frame state resets the reference-selection distributions' update-rate history.
/// </summary> /// </summary>
[Fact] [Fact]
public void FrameEntropySnapshotResetsReferenceSelectionUpdateCounts() public void EntropySnapshotResetsReferenceSelectionCounts()
{ {
const int updateCount = 20; const int updateCount = 20;
Av1FrameEntropyContext source = new(0); Av1FrameEntropyContext source = new(0);

2
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SymbolContextTests.cs

@ -79,7 +79,7 @@ public class Av1SymbolContextTests
} }
/// <summary> /// <summary>
/// SVT: get_lower_levels_ctx_eob /// Computes the expected lower-level coefficient context at the end of a transform block.
/// </summary> /// </summary>
internal static int GetExpectedLowerLevelContextEndOfBlock(int blockWidthLog2, int height, int scanIndex) internal static int GetExpectedLowerLevelContextEndOfBlock(int blockWidthLog2, int height, int scanIndex)
{ {

4
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TemporalSegmentationTests.cs

@ -12,7 +12,7 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
/// <summary> /// <summary>
/// Verifies AV1 temporal segment-map prediction against libaom's decoder rules. /// Verifies AV1 temporal segment-map prediction against independent decoder results.
/// </summary> /// </summary>
[Trait("Format", "Avif")] [Trait("Format", "Avif")]
public class Av1TemporalSegmentationTests public class Av1TemporalSegmentationTests
@ -189,7 +189,7 @@ public class Av1TemporalSegmentationTests
/// Verifies that a skipped inter block uses the spatial predictor without reading a temporal-prediction symbol. /// Verifies that a skipped inter block uses the spatial predictor without reading a temporal-prediction symbol.
/// </summary> /// </summary>
[Fact] [Fact]
public void SkippedInterBlockClearsTemporalPredictionAndUsesSpatialSegment() public void SkippedInterUsesSpatialSegment()
{ {
ObuSequenceHeader sequenceHeader = CreateSequenceHeader(64, 64); ObuSequenceHeader sequenceHeader = CreateSequenceHeader(64, 64);
ObuFrameHeader frameHeader = CreateFrameHeader(16, 16, segmentationUpdateMap: 1, segmentationTemporalUpdate: 1); ObuFrameHeader frameHeader = CreateFrameHeader(16, 16, segmentationUpdateMap: 1, segmentationTemporalUpdate: 1);

6
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs

@ -35,10 +35,10 @@ public class Av1TilingTests
} }
/// <summary> /// <summary>
/// Verifies the decoded block geometry and prediction modes against libaom inspection output for a real AVIF image item. /// Verifies the decoded block geometry and prediction modes against the reference decoder inspection output for a real AVIF image item.
/// </summary> /// </summary>
[Fact] [Fact]
public void ParsedRealAvifModeMapMatchesLibaom() public void ParsedAvifModeMapMatchesReference()
{ {
string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Heif.ParisIccExifXmpAvif); string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Heif.ParisIccExifXmpAvif);
byte[] content = File.ReadAllBytes(filePath); byte[] content = File.ReadAllBytes(filePath);
@ -63,7 +63,7 @@ public class Av1TilingTests
} }
} }
// These counts come from the 102 by 76 mode-info maps emitted by libaom 3.14.1's inspect tool. // These counts come from independently inspected 102 by 76 mode-info maps.
int[] expectedBlockSizeCounts = new int[(int)Av1BlockSize.AllSizes]; int[] expectedBlockSizeCounts = new int[(int)Av1BlockSize.AllSizes];
expectedBlockSizeCounts[(int)Av1BlockSize.Block8x8] = 3176; expectedBlockSizeCounts[(int)Av1BlockSize.Block8x8] = 3176;
expectedBlockSizeCounts[(int)Av1BlockSize.Block8x16] = 48; expectedBlockSizeCounts[(int)Av1BlockSize.Block8x16] = 48;

44
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1WarpedInterPredictorTests.cs

@ -21,21 +21,21 @@ public class Av1WarpedInterPredictorTests
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic; HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic;
/// <summary> /// <summary>
/// Verifies exact 8-bit native and compound prediction against the current libaom scalar equations. /// Verifies exact 8-bit native and compound prediction against independent scalar equations.
/// </summary> /// </summary>
[Fact] [Fact]
public void ByteNativeAndCompoundPredictionMatchesCurrentLibaomOracleAcrossIntrinsicConfigurations() public void BytePredictionMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateBytePrediction, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateBytePrediction, PredictorConfigurations);
/// <summary> /// <summary>
/// Verifies exact 8-, 10-, and 12-bit native and compound prediction against the current libaom scalar equations. /// Verifies exact 8-, 10-, and 12-bit native and compound prediction against independent scalar equations.
/// </summary> /// </summary>
[Fact] [Fact]
public void HighBitDepthNativeAndCompoundPredictionMatchesCurrentLibaomOracleAcrossIntrinsicConfigurations() public void HighBitDepthPredictionMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthPrediction, PredictorConfigurations); => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthPrediction, PredictorConfigurations);
/// <summary> /// <summary>
/// Applies the current-libaom multi-sample affine model to deterministic byte storage. /// Applies the reference multi-sample affine model to deterministic byte storage.
/// </summary> /// </summary>
private static void ValidateBytePrediction() private static void ValidateBytePrediction()
{ {
@ -54,7 +54,7 @@ public class Av1WarpedInterPredictorTests
} }
} }
Av1GlobalMotionParameters parameters = CreateCurrentLibaomParameters(); Av1GlobalMotionParameters parameters = CreateReferenceParameters();
for (int subsampling = 0; subsampling <= 1; subsampling++) for (int subsampling = 0; subsampling <= 1; subsampling++)
{ {
byte[] expected = new byte[destinationStride * height]; byte[] expected = new byte[destinationStride * height];
@ -64,7 +64,7 @@ public class Av1WarpedInterPredictorTests
short[] actualScratch = new short[Av1WarpedInterPredictor.WarpedScratchLength]; short[] actualScratch = new short[Av1WarpedInterPredictor.WarpedScratchLength];
Point destinationPosition = subsampling == 0 ? new Point(32, 24) : new Point(16, 12); Point destinationPosition = subsampling == 0 ? new Point(32, 24) : new Point(16, 12);
PredictCurrentLibaomReference( PredictReference(
source, source,
sourceStride, sourceStride,
new Point(padding, padding), new Point(padding, padding),
@ -104,7 +104,7 @@ public class Av1WarpedInterPredictorTests
Array.Fill(expectedCompound, (ushort)0xDEAD); Array.Fill(expectedCompound, (ushort)0xDEAD);
Array.Fill(actualCompound, (ushort)0xDEAD); Array.Fill(actualCompound, (ushort)0xDEAD);
PredictCurrentLibaomReference( PredictReference(
source, source,
sourceStride, sourceStride,
new Point(padding, padding), new Point(padding, padding),
@ -142,7 +142,7 @@ public class Av1WarpedInterPredictorTests
} }
/// <summary> /// <summary>
/// Applies the current-libaom multi-sample affine model to every supported high-bit-depth precision. /// Applies the multi-sample affine model to every supported high-bit-depth precision.
/// </summary> /// </summary>
private static void ValidateHighBitDepthPrediction() private static void ValidateHighBitDepthPrediction()
{ {
@ -152,7 +152,7 @@ public class Av1WarpedInterPredictorTests
const int width = 13; const int width = 13;
const int height = 11; const int height = 11;
const int destinationStride = width + 7; const int destinationStride = width + 7;
Av1GlobalMotionParameters parameters = CreateCurrentLibaomParameters(); Av1GlobalMotionParameters parameters = CreateReferenceParameters();
foreach (int bitDepth in new[] { 8, 10, 12 }) foreach (int bitDepth in new[] { 8, 10, 12 })
{ {
int maximum = (1 << bitDepth) - 1; int maximum = (1 << bitDepth) - 1;
@ -175,7 +175,7 @@ public class Av1WarpedInterPredictorTests
short[] actualScratch = new short[Av1WarpedInterPredictor.WarpedScratchLength]; short[] actualScratch = new short[Av1WarpedInterPredictor.WarpedScratchLength];
Point destinationPosition = subsampling == 0 ? new Point(32, 24) : new Point(16, 12); Point destinationPosition = subsampling == 0 ? new Point(32, 24) : new Point(16, 12);
PredictCurrentLibaomReference( PredictReference(
source, source,
sourceStride, sourceStride,
new Point(padding, padding), new Point(padding, padding),
@ -216,7 +216,7 @@ public class Av1WarpedInterPredictorTests
Array.Fill(expectedCompound, (ushort)0xDEAD); Array.Fill(expectedCompound, (ushort)0xDEAD);
Array.Fill(actualCompound, (ushort)0xDEAD); Array.Fill(actualCompound, (ushort)0xDEAD);
PredictCurrentLibaomReference( PredictReference(
source, source,
sourceStride, sourceStride,
new Point(padding, padding), new Point(padding, padding),
@ -256,9 +256,9 @@ public class Av1WarpedInterPredictorTests
} }
/// <summary> /// <summary>
/// Reconstructs one warped block by directly transcribing current libaom's scalar affine loops. /// Reconstructs one warped block with scalar affine loops.
/// </summary> /// </summary>
private static void PredictCurrentLibaomReference<TSource, TDestination>( private static void PredictReference<TSource, TDestination>(
ReadOnlySpan<TSource> source, ReadOnlySpan<TSource> source,
int sourceStride, int sourceStride,
Point sourceOrigin, Point sourceOrigin,
@ -285,7 +285,7 @@ public class Av1WarpedInterPredictorTests
const int pixelPrecisionShifts = 64; const int pixelPrecisionShifts = 64;
Span<int> intermediate = stackalloc int[15 * tileSize]; Span<int> intermediate = stackalloc int[15 * tileSize];
// Current libaom raises round0 by two for 12-bit sources so the biased horizontal intermediate // The reference decoder raises round0 by two for 12-bit sources so the biased horizontal intermediate
// remains representable in 16 bits, then removes those two bits from the vertical rounding. // remains representable in 16 bits, then removes those two bits from the vertical rounding.
int intermediateRange = bitDepth + filterBits - 3 + 2; int intermediateRange = bitDepth + filterBits - 3 + 2;
int round0 = 3 + Math.Max(intermediateRange - 16, 0); int round0 = 3 + Math.Max(intermediateRange - 16, 0);
@ -334,7 +334,7 @@ public class Av1WarpedInterPredictorTests
((sourceOrigin.Y + sourceY) * sourceStride) + sourceOrigin.X + sampleX; ((sourceOrigin.Y + sourceY) * sourceStride) + sourceOrigin.X + sampleX;
int sample = int.CreateChecked(source[sourceIndex]); int sample = int.CreateChecked(source[sourceIndex]);
sum += sample * CurrentLibaomWarpedFilter[coefficientOffset + tap]; sum += sample * ReferenceWarpedFilter[coefficientOffset + tap];
} }
intermediate[((row + 7) * tileSize) + column + 4] = RoundPowerOfTwo(sum, round0); intermediate[((row + 7) * tileSize) + column + 4] = RoundPowerOfTwo(sum, round0);
@ -355,7 +355,7 @@ public class Av1WarpedInterPredictorTests
for (int tap = 0; tap < filterTaps; tap++) for (int tap = 0; tap < filterTaps; tap++)
{ {
int intermediateIndex = ((row + tap + 4) * tileSize) + column + 4; int intermediateIndex = ((row + tap + 4) * tileSize) + column + 4;
sum += intermediate[intermediateIndex] * CurrentLibaomWarpedFilter[coefficientOffset + tap]; sum += intermediate[intermediateIndex] * ReferenceWarpedFilter[coefficientOffset + tap];
} }
int value = RoundPowerOfTwo(sum, verticalRound); int value = RoundPowerOfTwo(sum, verticalRound);
@ -381,9 +381,9 @@ public class Av1WarpedInterPredictorTests
} }
/// <summary> /// <summary>
/// Gets the 193 current-libaom eight-tap warped-filter phases used by the independent scalar oracle. /// Gets the 193 reference eight-tap warped-filter phases used by the independent scalar oracle.
/// </summary> /// </summary>
private static ReadOnlySpan<short> CurrentLibaomWarpedFilter => private static ReadOnlySpan<short> ReferenceWarpedFilter =>
[ [
0, 0, 127, 1, 0, 0, 0, 0, 0, 0, 127, 1, 0, 0, 0, 0,
0, -1, 127, 2, 0, 0, 0, 0, 0, -1, 127, 2, 0, 0, 0, 0,
@ -581,15 +581,15 @@ public class Av1WarpedInterPredictorTests
]; ];
/// <summary> /// <summary>
/// Divides a signed fixed-point value by a power of two with current libaom's rounding. /// Divides a signed fixed-point value by a power of two with the reference decoder's rounding.
/// </summary> /// </summary>
private static int RoundPowerOfTwo(int value, int bitCount) private static int RoundPowerOfTwo(int value, int bitCount)
=> (value + (1 << (bitCount - 1))) >> bitCount; => (value + (1 << (bitCount - 1))) >> bitCount;
/// <summary> /// <summary>
/// Creates one nontrivial affine model traced from the current-libaom-verified two-frame local-warp fixture. /// Creates one nontrivial affine model traced from the independently verified two-frame local-warp fixture.
/// </summary> /// </summary>
private static Av1GlobalMotionParameters CreateCurrentLibaomParameters() private static Av1GlobalMotionParameters CreateReferenceParameters()
{ {
Av1GlobalMotionParameters parameters = Av1GlobalMotionParameters.Identity; Av1GlobalMotionParameters parameters = Av1GlobalMotionParameters.Identity;
parameters.Type = Av1GlobalMotionType.Affine; parameters.Type = Av1GlobalMotionType.Affine;

10
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1YuvConverterTests.cs

@ -264,7 +264,7 @@ public class Av1YuvConverterTests
/// available intrinsic width and the scalar fallback. /// available intrinsic width and the scalar fallback.
/// </summary> /// </summary>
[Fact] [Fact]
public void ScaleSelectedSpatialLayerMatchesPinnedLibyuvAcrossIntrinsicWidths() public void ScaleSelectedSpatialLayerMatchesReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateSelectedSpatialLayerScaling, ValidateSelectedSpatialLayerScaling,
AlphaConfigurations); AlphaConfigurations);
@ -749,7 +749,7 @@ public class Av1YuvConverterTests
[InlineData(Av1BitDepth.EightBit, ObuMatrixCoefficients.YCgCoRo, 31, 65, 255)] [InlineData(Av1BitDepth.EightBit, ObuMatrixCoefficients.YCgCoRo, 31, 65, 255)]
[InlineData(Av1BitDepth.TenBit, ObuMatrixCoefficients.YCgCoRo, 127, 257, 1023)] [InlineData(Av1BitDepth.TenBit, ObuMatrixCoefficients.YCgCoRo, 127, 257, 1023)]
[InlineData(Av1BitDepth.TwelveBit, ObuMatrixCoefficients.YCgCoRo, 511, 1025, 4095)] [InlineData(Av1BitDepth.TwelveBit, ObuMatrixCoefficients.YCgCoRo, 511, 1025, 4095)]
public void ReversibleYCgCoMatchesKnownPureRedValuesInBothDirections( public void ReversibleYCgCoMatchesPureRed(
int bitDepth, int bitDepth,
int matrixCoefficients, int matrixCoefficients,
int expectedY, int expectedY,
@ -1028,7 +1028,7 @@ public class Av1YuvConverterTests
/// without hardware intrinsics. /// without hardware intrinsics.
/// </summary> /// </summary>
[Fact] [Fact]
public void ComposeAlphaMapsEightBitLumaExactlyAcrossIntrinsicWidths() public void ComposeAlphaMapsEightBitLumaExactly()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateEightBitAlphaComposition, ValidateEightBitAlphaComposition,
AlphaConfigurations); AlphaConfigurations);
@ -1038,7 +1038,7 @@ public class Av1YuvConverterTests
/// with and without hardware intrinsics. /// with and without hardware intrinsics.
/// </summary> /// </summary>
[Fact] [Fact]
public void ComposeAlphaScalesHighBitDepthLumaAcrossIntrinsicWidths() public void ComposeAlphaScalesHighBitDepthLuma()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateHighBitDepthAlphaScaling, ValidateHighBitDepthAlphaScaling,
AlphaConfigurations); AlphaConfigurations);
@ -1057,7 +1057,7 @@ public class Av1YuvConverterTests
/// Verifies exact limited-range endpoints and out-of-range clamping for every supported AV1 alpha bit depth. /// Verifies exact limited-range endpoints and out-of-range clamping for every supported AV1 alpha bit depth.
/// </summary> /// </summary>
[Fact] [Fact]
public void ComposeAlphaExpandsLimitedRangeAcrossIntrinsicWidths() public void ComposeAlphaExpandsLimitedRange()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature( => FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateLimitedRangeAlphaComposition, ValidateLimitedRangeAlphaComposition,
AlphaConfigurations); AlphaConfigurations);

18
tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
public class ObuFrameHeaderTests public class ObuFrameHeaderTests
{ {
/// <summary> /// <summary>
/// Identifies one current-libaom sequence-header conformance condition used by the malformed-input theory. /// Identifies one reference sequence-header conformance condition used by the malformed-input theory.
/// </summary> /// </summary>
public enum InvalidSequenceHeaderCase public enum InvalidSequenceHeaderCase
{ {
@ -258,7 +258,7 @@ public class ObuFrameHeaderTests
} }
/// <summary> /// <summary>
/// Verifies current-libaom sequence-header conformance failures through the complete bounded OBU parser. /// Verifies reference sequence-header conformance failures through the complete bounded OBU parser.
/// </summary> /// </summary>
/// <param name="invalidCase">The single invalid syntax condition encoded into an otherwise valid sequence header.</param> /// <param name="invalidCase">The single invalid syntax condition encoded into an otherwise valid sequence header.</param>
[Theory] [Theory]
@ -270,7 +270,7 @@ public class ObuFrameHeaderTests
[InlineData(InvalidSequenceHeaderCase.OverflowingTicksPerPicture)] [InlineData(InvalidSequenceHeaderCase.OverflowingTicksPerPicture)]
[InlineData(InvalidSequenceHeaderCase.MainProfileSrgbIdentity)] [InlineData(InvalidSequenceHeaderCase.MainProfileSrgbIdentity)]
[InlineData(InvalidSequenceHeaderCase.SubsampledIdentityMatrix)] [InlineData(InvalidSequenceHeaderCase.SubsampledIdentityMatrix)]
public void ReadSequenceHeaderRejectsCurrentLibaomConformanceFailure(InvalidSequenceHeaderCase invalidCase) public void ReadSequenceHeaderRejectsConformanceFailure(InvalidSequenceHeaderCase invalidCase)
{ {
byte[] bitStream = CreateNonReducedSequenceHeaderObu(invalidCase); byte[] bitStream = CreateNonReducedSequenceHeaderObu(invalidCase);
@ -313,10 +313,10 @@ public class ObuFrameHeaderTests
} }
/// <summary> /// <summary>
/// Verifies that metadata-type LEB128 values obey current libaom's shared unsigned 32-bit limit. /// Verifies that metadata-type LEB128 values obey the reference decoder's shared unsigned 32-bit limit.
/// </summary> /// </summary>
[Fact] [Fact]
public void ValidateItemDataRejectsMetadataTypeAboveCurrentLibaomLimit() public void ValidateItemDataRejectsMetadataTypeAboveLimit()
{ {
byte[] bitStream = byte[] bitStream =
[ [
@ -425,7 +425,7 @@ public class ObuFrameHeaderTests
/// Verifies that the reduced sequence syntax cannot be used without declaring a still picture. /// Verifies that the reduced sequence syntax cannot be used without declaring a still picture.
/// </summary> /// </summary>
[Fact] [Fact]
public void ReadReducedHeaderWithoutStillPictureThrowsInvalidImageContent() public void ReducedHeaderWithoutStillPictureThrows()
{ {
const int sequenceHeaderPayloadOffset = 2; const int sequenceHeaderPayloadOffset = 2;
byte[] bitStream = [.. DefaultSequenceHeaderBitStream]; byte[] bitStream = [.. DefaultSequenceHeaderBitStream];
@ -445,7 +445,7 @@ public class ObuFrameHeaderTests
[InlineData(0b1000_0000)] // show_existing_frame = 1 [InlineData(0b1000_0000)] // show_existing_frame = 1
[InlineData(0b0101_0000)] // frame_type = INTRA_ONLY_FRAME, show_frame = 1 [InlineData(0b0101_0000)] // frame_type = INTRA_ONLY_FRAME, show_frame = 1
[InlineData(0b0000_0000)] // frame_type = KEY_FRAME, show_frame = 0 [InlineData(0b0000_0000)] // frame_type = KEY_FRAME, show_frame = 0
public void ReadInvalidStillPictureFramePrefixThrowsInvalidImageContent(int invalidFramePrefix) public void InvalidStillPicturePrefixThrows(int invalidFramePrefix)
{ {
ObuSequenceHeader sequenceHeader = GetDefaultSequenceHeader(); ObuSequenceHeader sequenceHeader = GetDefaultSequenceHeader();
sequenceHeader.IsReducedStillPictureHeader = false; sequenceHeader.IsReducedStillPictureHeader = false;
@ -513,7 +513,7 @@ public class ObuFrameHeaderTests
} }
/// <summary> /// <summary>
/// Verifies current libaom's doubled minimum inner-tile width for a super-resolution-scaled frame. /// Verifies the reference decoder's doubled minimum inner-tile width for a super-resolution-scaled frame.
/// </summary> /// </summary>
[Fact] [Fact]
public void ReadTileInfoRejectsNarrowSuperResolutionInnerTile() public void ReadTileInfoRejectsNarrowSuperResolutionInnerTile()
@ -693,7 +693,7 @@ public class ObuFrameHeaderTests
writer.WriteBoolean(overflowingTicksPerPicture); writer.WriteBoolean(overflowingTicksPerPicture);
if (overflowingTicksPerPicture) if (overflowingTicksPerPicture)
{ {
// Thirty-two leading zeros are the UVLC sentinel which current libaom rejects as UINT32_MAX. // Thirty-two leading zeros are the UVLC sentinel which the reference decoder rejects as UINT32_MAX.
writer.WriteLiteral(0U, 32); writer.WriteLiteral(0U, 32);
} }

6
tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameLifecycleTests.cs

@ -96,7 +96,7 @@ public class ObuFrameLifecycleTests
/// Verifies that invalid trailing data in a subsequent sequence header is rejected before that sequence can create frame state. /// Verifies that invalid trailing data in a subsequent sequence header is rejected before that sequence can create frame state.
/// </summary> /// </summary>
[Fact] [Fact]
public void ReadAllRejectsMalformedSubsequentSequenceBeforeCreatingItsFrameReader() public void RejectsMalformedFollowingSequenceBeforeFrameReader()
{ {
byte[] firstLayer = ProgressiveTwoFrameObuStream[..FirstProgressiveLayerLength].ToArray(); byte[] firstLayer = ProgressiveTwoFrameObuStream[..FirstProgressiveLayerLength].ToArray();
byte[] malformedSequence = AddInvalidSequenceHeaderTrailingByte(ProgressiveTwoFrameObuStream[..ProgressiveSequencePrefixLength].ToArray()); byte[] malformedSequence = AddInvalidSequenceHeaderTrailingByte(ProgressiveTwoFrameObuStream[..ProgressiveSequencePrefixLength].ToArray());
@ -148,7 +148,7 @@ public class ObuFrameLifecycleTests
/// Verifies that malformed data after a completed real frame invalidates retained state without preventing reuse of the parser. /// Verifies that malformed data after a completed real frame invalidates retained state without preventing reuse of the parser.
/// </summary> /// </summary>
[Fact] [Fact]
public void ReadAllClearsCompletedFrameStateWhenFollowingObuHeaderIsInvalid() public void ClearsCompletedFrameStateAfterInvalidObuHeader()
{ {
byte[] bitStream = [.. ProgressiveTwoFrameObuStream[..FirstProgressiveLayerLength], ForbiddenObuHeader]; byte[] bitStream = [.. ProgressiveTwoFrameObuStream[..FirstProgressiveLayerLength], ForbiddenObuHeader];
using Av1ReferenceFrameStore referenceFrames = new(); using Av1ReferenceFrameStore referenceFrames = new();
@ -176,7 +176,7 @@ public class ObuFrameLifecycleTests
[ [
.. ProgressiveTwoFrameObuStream[..FirstProgressiveLayerLength], .. ProgressiveTwoFrameObuStream[..FirstProgressiveLayerLength],
// A one-byte combined-frame payload selecting retained slot zero. Current libaom rejects this form // A one-byte combined-frame payload selecting retained slot zero. The reference decoder rejects this form
// because show_existing_frame is permitted only in a standalone frame-header OBU. // because show_existing_frame is permitted only in a standalone frame-header OBU.
0x32, 0x01, 0x80 0x32, 0x01, 0x80
]; ];

2
tests/ImageSharp.Tests/Formats/Heif/Av1/ObuSkipModeParametersTests.cs

@ -116,7 +116,7 @@ public class ObuSkipModeParametersTests
/// Verifies that a forward-only frame selects the two closest distinct past reference orders. /// Verifies that a forward-only frame selects the two closest distinct past reference orders.
/// </summary> /// </summary>
[Fact] [Fact]
public void DeriveSelectsTwoNearestForwardReferencesWhenNoBackwardReferenceExists() public void SelectsTwoForwardReferencesWithoutBackwardReference()
{ {
ObuOrderHintInfo orderHintInfo = CreateOrderHintInfo(); ObuOrderHintInfo orderHintInfo = CreateOrderHintInfo();
ObuFrameHeader frameHeader = CreateInterFrame(8, [7, 3, 6, 2, 1, 5, 4]); ObuFrameHeader frameHeader = CreateInterFrame(8, [7, 3, 6, 2, 1, 5, 4]);

4
tests/ImageSharp.Tests/TestImages.cs

@ -1292,13 +1292,13 @@ public static class TestImages
public const string ParisIccExifXmpAvif = "Heif/paris_icc_exif_xmp.avif"; public const string ParisIccExifXmpAvif = "Heif/paris_icc_exif_xmp.avif";
public const string ParisIccExifXmpPng = "Heif/paris_icc_exif_xmp.png"; public const string ParisIccExifXmpPng = "Heif/paris_icc_exif_xmp.png";
// Independently encoded from Png.Icc.Perceptual with libavif 1.4.2 and libaom 3.14.1. Unlike the // Independently encoded from Png.Icc.Perceptual. Unlike the
// canonical-sRGB Paris profile, this perceptual profile requires an observable color conversion. // canonical-sRGB Paris profile, this perceptual profile requires an observable color conversion.
public const string PerceptualIccAvif = "Heif/perceptual_icc.avif"; public const string PerceptualIccAvif = "Heif/perceptual_icc.avif";
public const string PerceptualIccGridAvif = "Heif/perceptual_icc_grid.avif"; public const string PerceptualIccGridAvif = "Heif/perceptual_icc_grid.avif";
public const string PerceptualIccSequenceAvif = "Heif/perceptual_icc_sequence.avif"; public const string PerceptualIccSequenceAvif = "Heif/perceptual_icc_sequence.avif";
// Independently encoded from Png.Ducky with libavif 1.4.2 and libaom 3.14.1. The ROMM RGB profile is // Independently encoded from Png.Ducky. The ROMM RGB profile is
// deliberately non-sRGB so that color conversion is observable while the auxiliary alpha item is composed. // deliberately non-sRGB so that color conversion is observable while the auxiliary alpha item is composed.
public const string DuckyRommIccAlphaAvif = "Heif/ducky_romm_icc_alpha.avif"; public const string DuckyRommIccAlphaAvif = "Heif/ducky_romm_icc_alpha.avif";

0
tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeWithActiveCdefMatchesPinnedLibavifPresentation_Rgba32_libavif-cdef-cosmos-10b.png → tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeCdefMatchesReference_Rgba32_libavif-cdef-cosmos-10b.png

0
tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeWithActiveCdefMatchesPinnedLibavifPresentation_Rgba32_libavif-cdef-cosmos-12b.png → tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeCdefMatchesReference_Rgba32_libavif-cdef-cosmos-12b.png

0
tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeWithActiveCdefMatchesPinnedLibavifPresentation_Rgba32_libavif-cdef-kodim23-8b.png → tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeCdefMatchesReference_Rgba32_libavif-cdef-kodim23-8b.png

0
tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeRealLibavifSequenceWithEqualAverageCompoundMatchesVerifiedReferences_Rgba32_libavif-webp-logo-average-compound.png → tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeEqualAverageCompoundMatchesReference_Rgba32_libavif-webp-logo-average-compound.png

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save