Browse Source

Complete AV1 single-reference decoding checkpoint

pull/2633/head
James Jackson-South 3 days ago
parent
commit
54bb6cbe59
  1. 182
      HEIF_IMPLEMENTATION_PLAN.md
  2. 14
      src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs
  3. 2
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  4. 35
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1FrameBufferTests.cs
  5. 4
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InterFrameModeInfoTests.cs
  6. 32
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
  7. 8
      tests/ImageSharp.Tests/Formats/Heif/HeifDecoderTests.cs

182
HEIF_IMPLEMENTATION_PLAN.md

@ -8,28 +8,35 @@ This plan is the authoritative delivery checklist. A source file, unit test, bui
## Source authority ## Source authority
- AV1 codec syntax, tables, fixed-point arithmetic, prediction, transforms, entropy behavior, filters, and lifecycle behavior must be ported and checked only against libaom commit 03087864cf4bea6abb0d28f95cf7843511413d8f. - AV1 codec syntax, tables, fixed-point arithmetic, prediction, transforms, entropy behavior, filters, encoder decisions, and lifecycle behavior must be ported and checked only against the current `main` branch of the official libaom checkout at `D:\GitHub\AOMediaCodec\aom`.
- The AV1 specification is the normative behavioral description. It does not authorize copying an implementation from another codec library. - Libaom is the sole external codec implementation source. Do not use HM, libheif, FFmpeg, GPAC, SVT-AV1, dav1d, libgav1, or any other codec implementation as an algorithm, arithmetic, output, or architecture reference.
- Existing ImageSharp and JPEG code is the architecture, allocator, SIMD dispatch, pixel conversion, and test-API pattern. It is not an alternate AV1 algorithm source. - Existing ImageSharp and JPEG code is authoritative only for ImageSharp architecture, allocator ownership, SIMD dispatch, pixel conversion, and test API patterns. It is not an alternate AV1 algorithm source.
- Production code must not load, invoke, install, or fall back to a native codec. - Production code must not load, invoke, install, or fall back to a native codec.
- External artifacts may be retained only as test inputs or expected outputs with recorded provenance. They must never become an implementation source. - Existing independent container files may be used only as interoperability inputs. Native AV1 expected output must be generated by the current libaom `main` checkout, and no independent decoder output may substitute for it.
Reference checkout evidence on 2026-08-31:
- `D:\GitHub\AOMediaCodec\aom` is attached to `main`, clean, and aligned with `origin/main` after a fresh fetch.
- Both `HEAD` and `origin/main` resolved to `441c439b9916474cac15d2822af47a9ad70674a8`. This records the tree audited on that date; it is not a pin and must not prevent later work from updating to the then-current `main`.
## Status notation ## Status notation
- [x] Verified: the current behavior has exact evidence from the pinned reference and the evidence proves the production contract. - [x] Verified: the current behavior has exact evidence from the current libaom `main` tree and the evidence proves the production contract.
- [~] Locally implemented, checkpoint open: production source exists, but current-tree verification is missing or a known audit issue invalidates the checkpoint. - [~] Locally implemented, checkpoint open: production source exists, but current-tree verification is missing or a known audit issue invalidates the checkpoint.
- [ ] Remaining: the production behavior is absent, incomplete, or has not reached its required implementation boundary. - [ ] Remaining: the production behavior is absent, incomplete, or has not reached its required implementation boundary.
## Current source reconciliation ## Current source reconciliation
Reconciled with the worktree on 2026-08-30. Reconciled with the worktree on 2026-08-31.
- [~] The bounded container reader, still-image path, sequence parser, AV1 decoder, color pipeline, presentation pipeline, and broad AV1 test suite exist locally. - [~] The bounded container reader, still-image path, sequence parser, AV1 decoder, color pipeline, presentation pipeline, and broad AV1 test suite exist locally.
- [~] The inter-frame decoder contains implementations for single-reference prediction, compound references, inter-intra prediction, selectable compound blending, OBMC, scaled references, local warped motion, and global motion. These downstream paths must not be called verified until the single-reference checkpoint below is corrected and rerun. - [~] The inter-frame decoder contains implementations for single-reference prediction, compound references, inter-intra prediction, selectable compound blending, OBMC, scaled references, local warped motion, and global motion. These downstream paths must not be called verified until the single-reference checkpoint below is corrected and rerun.
- [~] Loop filtering, CDEF, super-resolution, restoration, film grain, layered presentation, alpha composition, and color conversion exist locally. Shared-source cleanup changed the current tree, so final production-path verification is open. - [~] Loop filtering, CDEF, super-resolution, restoration, film grain, layered presentation, alpha composition, and color conversion exist locally. Shared-source cleanup changed the current tree, so final production-path verification is open.
- [~] AV1 writer primitives, forward transforms, symbol encoding, and tile-writing source exist locally, but they are not connected to the public encoder. - [~] AV1 writer primitives, forward transforms, symbol encoding, and tile-writing source exist locally, but they are not connected to the public encoder.
- [ ] The public AV1 encoder is not implemented. HeifEncoderCore.Encode throws NotSupportedException when AV1 is selected. - [ ] The public AV1 encoder is not implemented. HeifEncoderCore.Encode throws NotSupportedException when AV1 is selected.
- [x] Removed codec production code, registrations, tests, benchmarks, fixtures, reference outputs, downloaded tools, downloaded source trees, and notices have been manually deleted and verified by the cleanup evidence below. - [x] Patented codec production code, registrations, tests, benchmarks, fixtures, reference outputs, and notices were manually deleted and committed by `78a74d448`.
- [x] Remaining task-created HM, HEVC, libheif, GPAC, Nokia, FFmpeg, Pillow HEIF, libavif-build, and libjpeg-build directories were traced to their creation commands in the recovered Codex session history and deleted on 2026-08-31. The user-provided repositories and all libaom-only source, build, and reference data were left untouched.
- [~] A PNG metadata-suppression fix and three HEIF/AV1 diagnostic-save call-site corrections are implemented in the current worktree. The exact 34 cases that failed in the net11.0 ARM CI job now pass in Release, but these changes are not yet committed and are not decoder or encoder completion evidence.
- [ ] The complete decoder and encoder release matrix is not complete. - [ ] The complete decoder and encoder release matrix is not complete.
## Immediate execution queue ## Immediate execution queue
@ -40,68 +47,131 @@ Work must proceed in this order. Do not skip to a later item while an earlier ch
- [x] Remove production types, registrations, constants, parser branches, properties, tests, benchmarks, fixtures, reference outputs, notices, and documentation for removed codec work. - [x] Remove production types, registrations, constants, parser branches, properties, tests, benchmarks, fixtures, reference outputs, notices, and documentation for removed codec work.
- [x] Remove downloaded non-libaom reference source, tools, generated outputs, and local installations. - [x] Remove downloaded non-libaom reference source, tools, generated outputs, and local installations.
- [x] Retain the pinned libaom source and build artifacts required for AV1 verification. - [x] Retain the official current-main libaom checkout and libaom-only build artifacts required for AV1 verification.
- [x] Retain user-supplied AV1 fixtures and their recorded expected outputs. - [x] Retain user-supplied AV1 fixtures and their recorded expected outputs.
- [x] Audit production source, tests, benchmarks, assets, project files, notices, and documentation for stale removed-code references. - [x] Audit production source, tests, benchmarks, assets, project files, notices, and documentation for stale removed-code references.
- [x] Build the current source targets in Release with restore disabled, build servers disabled, and one MSBuild node. - [~] The prior cleanup tree built in Release with restore disabled, build servers disabled, and one MSBuild node. A fresh current-worktree build remains required after the local PNG cICP fix.
- [x] Run the focused AV1/container tests needed to prove the cleanup did not damage AVIF behavior. - [~] The prior focused AV1/container set passed, but it missed the net11.0 PNG cICP diagnostic-save failures. The exact 34 CI failures now pass after the local fix; the final current-worktree checkpoint set remains required.
- [x] Run scoped semantic and StyleCop inspection, whitespace inspection, and git diff --check. - [~] Roslynk currently reports zero compiler errors for the local fix, and `git diff --check` passes. Scoped StyleCop and final current-worktree inspection remain required.
- [x] Record the exact verified evidence in this plan. - [~] Record the final current-worktree cleanup and cICP evidence after the checkpoint verification completes.
Verified cleanup evidence on 2026-08-30: Historical cleanup evidence from 2026-08-30, retained with its limitation:
- Release source builds passed for net10.0 and net11.0 with zero warnings and zero errors. Both builds used `--no-restore`, `--disable-build-servers`, and one MSBuild node. - Release source builds passed for net10.0 and net11.0 with zero warnings and zero errors. Both builds used `--no-restore`, `--disable-build-servers`, and one MSBuild node.
- The focused net10.0 HEIF decoder, encoder, metadata, sequence-parser, and AV1 reconstruction set passed 221 of 221 tests with zero failures and zero skips. - The focused net10.0 HEIF decoder, encoder, metadata, sequence-parser, and AV1 reconstruction set passed 221 of 221 tests with zero failures and zero skips. It did not execute the net11.0 diagnostic-save path that later failed in CI.
- The Roslyn compiler and configured StyleCop analyzers accepted the changed production source. Roslynk's `open_solution` entry point was attempted separately but failed before returning a solution handle, so no Roslynk result is claimed. - The Roslyn compiler and configured StyleCop analyzers accepted the changed production source. Roslynk's `open_solution` entry point was attempted separately but failed before returning a solution handle, so no Roslynk result is claimed.
- The final text and filename audit found no removed-code references outside the unchanged repository and shared-infrastructure `.gitattributes` patterns. - The tracked-source text and filename audit found no removed-code references outside the unchanged repository and shared-infrastructure `.gitattributes` patterns. A later history reconstruction found ignored task-created reference directories that this audit missed; those directories were deleted on 2026-08-31.
- `git diff --check` passed and neither `.gitattributes` file changed. - `git diff --check` passed and neither `.gitattributes` file changed.
Current cICP failure correction evidence from 2026-08-31:
- The failure was not decoded HEIF metadata. `PngEncoderCore.WriteCicpChunk` ignored `PngChunkFilter.ExcludeAll`, so diagnostic PNG saves attempted to write a non-identity source matrix that PNG cannot represent.
- `PngEncoderCore` now honors the existing `SkipMetadata` contract for cICP, and the three affected HEIF/AV1 diagnostic saves explicitly use `PngEncoder { SkipMetadata = true }`. Actual comparisons and decoded-image metadata assertions remain unchanged.
- The direct embedded-ICC case and every row of the 12-case profile matrix passed: 13 of 13 net11.0 Release cases.
- The exact 34 cases reported by CI passed: 34 of 34 net11.0 Release cases, with zero failures and zero skips.
- Roslynk reported zero compiler errors after the fix, and `git diff --check` passed.
Recovered task-history evidence from 2026-08-31:
- The primary session beginning on 2026-08-24 was reopened from task ID `01a03239-831b-7831-84e7-7f6947279ccb`: 96,777 records, 295 turn contexts, 211 compactions, 190 user messages, 1,920 assistant messages, and 13,671 tool calls.
- The continuation beginning on 2026-08-27 was reopened from task ID `01a04314-f1c6-7133-b1bc-5c74a94dd714`: 61,129 records at the audit point, 166 turn contexts, 96 compactions, 223 user messages, 1,113 assistant messages, and 8,942 tool calls.
- The restored first session records the user selecting official AOM/libaom as the AV1 source after the ImageSharp discussion was inspected. It does not authorize another codec implementation as an AV1 source and does not authorize importing a patented codec.
- The restored tool calls identify the exact creation commands for the non-libaom source, tool, and output directories removed on 2026-08-31. No directory was selected for deletion from its name alone.
- The recovered Git sequence establishes that `78a74d448` removed the patented codec implementation and `92fa7a8ca` merged the later upstream ImageSharp changes. The current branch and worktree, not an older summary, remain authoritative.
### 2. Correct the single-reference inter-frame checkpoint ### 2. Correct the single-reference inter-frame checkpoint
The following findings are confirmed by direct source inspection and keep the checkpoint open. 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.
- [ ] Correct interpolation-filter syntax in Av1TileReader. - [x] Reconcile interpolation-filter syntax in `Av1TileReader` with current libaom `main`.
- Current source treats every global-motion type other than Translation as non-translational. - Current libaom `av1_is_interp_needed` calls `is_nontrans_global_motion`, whose loop rejects only `TRANSLATION`. Identity GLOBALMV therefore omits switchable-filter symbols.
- Pinned libaom omits interpolation-filter syntax only when the selected model type is greater than Translation. - Current `Av1TileReader` uses the same non-Translation classification. The existing Identity test leaves sentinel filter symbols unread, while the Translation test consumes them.
- Identity GLOBALMV blocks of sufficient size must consume switchable-filter symbols. - No production change is required. The stale test comment was changed from “pinned” to “current” libaom.
- Add production-path syntax coverage using the default Identity model. A test that forces Translation does not prove this rule. - [x] Reconcile both spatial single-reference extension loops in `Av1ReferenceMotionVectors` with current libaom `main`.
- [ ] Correct both spatial reference-MV extension loops in Av1ReferenceMotionVectors. - 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 source stops spatial extension when the stack reaches two entries. - Current `Av1ReferenceMotionVectors` uses the same two-entry stop condition and retains an eight-entry stack for earlier candidates and DRL selection.
- Pinned libaom extends the stack through MAX_REF_MV_STACK_SIZE, which is eight. - No production change is required. This remains spatial single-reference extension, not temporal extension.
- Preserve DRL candidates and consume every required DRL symbol. - [x] Establish and enforce the contiguous frame-plane invariant used by `Av1FrameBuffer` and inter reconstruction.
- Describe this as spatial single-reference extension, never temporal extension. - Every frame plane is allocated with `preferContiguosImageBuffers: true`, so a constrained allocator cannot split a representable padded plane into normal memory groups.
- [ ] Replace the contiguous-span dependency in Av1FrameBuffer and all affected inter reconstruction callers. - `Av1FrameBuffer` now rejects an external frame geometry whose padded plane reaches the `int.MaxValue` fallback boundary before any allocation. This makes every direct `DangerousGetSingleSpan` call an enforced owner invariant rather than a memory-group accident.
- Current GetPaddedPlaneSpan calls DangerousGetSingleSpan. - `ConstructorRequestsContiguousPaddedPlanes` proves that a plane larger than the allocator's group capacity is one group. `ConstructorRejectsPaddedPlaneThatCannotBeContiguous` proves that an unrepresentable plane is rejected before allocation.
- Buffer2D may use multiple memory groups under a constrained allocator. - The production path performs no plane copy and no per-block, per-row, or per-scanline allocation.
- Implement an efficient group-safe row-oriented contract using established ImageSharp Buffer2D access patterns, or prove and enforce a real contiguous-allocation invariant at the allocator boundary. - [x] Prove the real `Av1BlockDecoder.DecodeBlock` inter-reconstruction branch.
- Do not copy planes and do not allocate per block, row, or scanline.
- Audit direct DangerousGetSingleSpan use in reconstruction, reference-border extension, film grain, copying, and encoder work rather than fixing only one wrapper.
- [ ] Prove the real Av1BlockDecoder.DecodeBlock inter-reconstruction branch.
- Decode the progressive dependent-frame fixture through the complete public production path. - Decode the progressive dependent-frame fixture through the complete public production path.
- Compare the final frame's native Y, Cb, and Cr planes exactly with pinned 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.
- [ ] Prove motion-field ownership and lifetime. - [x] Prove motion-field ownership and lifetime.
- 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.
- [ ] Correct stale documentation. - [x] Correct stale documentation.
- 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.
Checkpoint gate: Checkpoint gate:
- [ ] Default Identity and Translation GLOBALMV syntax cases pass. - [x] Default Identity-omission and Translation-consumption GLOBALMV syntax cases pass in the focused current-tree run.
- [ ] Eight-entry spatial extension and DRL syntax cases pass. - [x] Two-entry spatial single-reference extension passes; current-main source inspection confirms the separate eight-entry overall stack capacity and DRL access.
- [ ] The exact dependent-frame native-plane comparison passes. - [x] The exact dependent-frame native-plane comparison passes.
- [ ] The established exact presentation comparison passes. - [x] The established exact presentation comparison passes.
- [ ] 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.
- [ ] Constrained multi-group allocation passes without copying or per-block allocation. - [x] Constrained allocation preserves the enforced single-group plane invariant without copying or per-block allocation.
- [ ] Motion-field allocation tracking is balanced across success and failure. - [x] Motion-field allocation tracking is balanced across success and failure.
- [ ] Release builds for net10.0 and net11.0 pass with zero errors. - [x] Release builds for net10.0 and net11.0 pass with zero errors.
- [ ] Focused Release tests pass with zero failures or skips. - [x] Focused Release tests pass with zero failures or skips.
- [ ] Scoped semantic, StyleCop, whitespace, and git diff checks pass. - [x] Scoped semantic, StyleCop, whitespace, and git diff checks pass.
- [ ] Only after all evidence is recorded may this checkpoint be committed. - [ ] Only after all evidence is recorded may this checkpoint be committed.
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`.
- 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.
- `DecodeProgressiveSingleReferenceMatchesPinnedReferences` 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.
- `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.
- 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.
- 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.
- `git diff --check` passed, and neither `.gitattributes` file changed.
Exact verification commands, run directly in the foreground from `D:\GitHub\ynse01\ImageSharp`:
```powershell
$env:MSBUILDUSESERVER = '0'
$env:DOTNET_CLI_USE_MSBUILD_SERVER = '0'
$env:DOTNET_CLI_HOME = 'D:\GitHub\ynse01\ImageSharp\.dotnet'
$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1'
$env:DOTNET_CLI_TELEMETRY_OPTOUT = '1'
$env:DOTNET_DbgEnableMiniDump = '0'
$env:COMPlus_DbgEnableMiniDump = '0'
$env:DOTNET_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.DecodeProgressiveSingleReferenceMatchesPinnedReferences|FullyQualifiedName~Av1ReconstructionConformanceTests.DecodeProgressiveSingleReferenceWithConstrainedAllocator|FullyQualifiedName~Av1ReconstructionConformanceTests.DecodeProgressiveSingleReferenceTracksMotionFieldResultOwnership'
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 test tests\ImageSharp.Tests\ImageSharp.Tests.csproj -c Release -f net10.0 --no-restore --disable-build-servers -m:1 --filter $heifCheckpointFilter --logger 'console;verbosity=minimal'
dotnet test tests\ImageSharp.Tests\ImageSharp.Tests.csproj -c Release -f net11.0 --no-restore --disable-build-servers -m:1 --filter $heifCheckpointFilter --logger 'console;verbosity=minimal'
```
```powershell
$aomVcVars = 'C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\VC\Auxiliary\Build\vcvars64.bat'
$aomCmake = 'C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe'
$aomEnvironment = & cmd.exe /d /s /c "`"$aomVcVars`" >nul && set"
foreach ($aomEntry in $aomEnvironment)
{
$aomParts = $aomEntry -split '=', 2
if ($aomParts.Length -eq 2)
{
[Environment]::SetEnvironmentVariable($aomParts[0], $aomParts[1], 'Process')
}
}
& $aomCmake --build artifacts\reference\aom-generic --target aomdec --config Release --parallel 1
& 'artifacts\reference\aom-generic\aomdec.exe' --codec=av1 --rawvideo --all-layers --threads=1 --row-mt=0 --output='artifacts\reference\aom-generic\progressive-current-main-all-layers.yuv' 'tests\Images\Input\Heif\Av1\Conformance\libavif-progressive-draw-points-8b.bit'
```
### 3. Reverify downstream inter prediction in recorded order ### 3. Reverify downstream inter prediction in recorded order
These implementations exist locally but inherit the open single-reference syntax, buffer, and ownership foundation. These implementations exist locally but inherit the open single-reference syntax, buffer, and ownership foundation.
@ -119,7 +189,7 @@ These implementations exist locally but inherit the open single-reference syntax
For every item: For every item:
- [ ] Trace syntax and arithmetic to the pinned libaom commit. - [ ] Trace syntax and arithmetic to the current libaom `main` tree.
- [ ] Execute the real production decoder path. - [ ] Execute the real production decoder path.
- [ ] Compare native planes exactly. - [ ] Compare native planes exactly.
- [ ] Compare presentation through the established reference-image API. - [ ] Compare presentation through the established reference-image API.
@ -131,8 +201,8 @@ For every item:
Previously verified algorithm checkpoints remain valuable evidence, but the final decoder gate requires a fresh current-tree run after the inter and cleanup corrections. Previously verified algorithm checkpoints remain valuable evidence, but the final decoder gate requires a fresh current-tree run after the inter and cleanup corrections.
- [x] Bounded OBU framing, sequence headers, frame headers, tile groups, alignment, and trailing-bit parsing have pinned-reference checkpoint evidence. - [~] Bounded OBU framing, sequence headers, frame headers, tile groups, alignment, and trailing-bit parsing have historical checkpoint evidence against an obsolete pinned tree. Re-audit the current libaom `main` implementation before restoring verified status.
- [x] Partition traversal, mode information, segmentation, delta quantization, transform-size selection, coefficient decoding, inverse quantization, and inverse transforms have pinned-reference checkpoint evidence. - [~] Partition traversal, mode information, segmentation, delta quantization, transform-size selection, coefficient decoding, inverse quantization, and inverse transforms have historical checkpoint evidence against an obsolete pinned tree. Re-audit the current libaom `main` implementation before restoring verified status.
- [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.
@ -148,7 +218,7 @@ Previously verified algorithm checkpoints remain valuable evidence, but the fina
Decoder exit gate: Decoder exit gate:
- [ ] Every supported native format and AV1 tool has exact pinned-libaom production-path evidence. - [ ] Every supported native format and AV1 tool has exact current-main libaom production-path evidence.
- [ ] Every supported presentation behavior has established reference-image evidence at the correct output precision. - [ ] Every supported presentation behavior has established reference-image evidence at the correct output precision.
- [ ] No decoder path relies on a native codec, copied plane, per-block allocation, or contiguous memory-group accident. - [ ] No decoder path relies on a native codec, copied plane, per-block allocation, or contiguous memory-group accident.
- [ ] All allocator ownership is deterministic and exactly once. - [ ] All allocator ownership is deterministic and exactly once.
@ -181,7 +251,7 @@ Writer primitives are not an encoder. The public encoder remains incomplete unti
- [ ] Implement legal deblocking, CDEF, restoration, super-resolution, and film-grain signaling decisions. - [ ] Implement legal deblocking, CDEF, restoration, super-resolution, and film-grain signaling decisions.
- [ ] Remove per-transform and per-block managed allocations from active encoder paths. - [ ] Remove per-transform and per-block managed allocations from active encoder paths.
- [ ] Use descending SIMD dispatch: Vector512, Vector256, Vector128, then scalar. - [ ] Use descending SIMD dispatch: Vector512, Vector256, Vector128, then scalar.
- [ ] Verify every SIMD operator with FeatureTestRunner and an independent scalar oracle shaped from the same pinned libaom behavior. - [ ] Verify every SIMD operator with FeatureTestRunner and an independent scalar oracle shaped from the same current-main libaom behavior.
### 7. Write complete AVIF output ### 7. Write complete AVIF output
@ -194,7 +264,7 @@ Writer primitives are not an encoder. The public encoder remains incomplete unti
Encoder exit gate: Encoder exit gate:
- [ ] Pinned libaom accepts every produced AV1 payload. - [ ] Current-main libaom accepts every produced AV1 payload.
- [ ] Lossless output is exact at native-plane and final-pixel precision. - [ ] Lossless output is exact at native-plane and final-pixel precision.
- [ ] Lossy output demonstrates recorded quality and effort tradeoffs with absolute size, quality, timing, and allocation evidence. - [ ] Lossy output demonstrates recorded quality and effort tradeoffs with absolute size, quality, timing, and allocation evidence.
- [ ] 8, 10, and 12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 outputs pass. - [ ] 8, 10, and 12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 outputs pass.
@ -222,7 +292,7 @@ Encoder exit gate:
- On internal types, use public members when other types consume them; reserve private members for type-local behavior. - On internal types, use public members when other types consume them; reserve private members for type-local behavior.
- Use established ImageSharp test data, allocator tracking, FeatureTestRunner, and reference-image comparison APIs. Do not build custom substitutes. - Use established ImageSharp test data, allocator tracking, FeatureTestRunner, and reference-image comparison APIs. Do not build custom substitutes.
- Public XML documentation describes observable behavior only. - Public XML documentation describes observable behavior only.
- Inline comments explain the pinned numerical rule, ownership boundary, edge extension, entropy ordering, or SIMD shape at technically complex points. - Inline comments explain the current-libaom numerical rule, ownership boundary, edge extension, entropy ordering, or SIMD shape at technically complex points.
- Every multiline statement or declaration is followed by vertical whitespace. - Every multiline statement or declaration is followed by vertical whitespace.
- Do not edit .gitattributes directly. - Do not edit .gitattributes directly.
- Do not install or download tools without explicit permission. - Do not install or download tools without explicit permission.
@ -236,13 +306,13 @@ Encoder exit gate:
- [ ] Focused encoder syntax, payload, container, precision, ownership, and option tests. - [ ] Focused encoder syntax, payload, container, precision, ownership, and option tests.
- [ ] FeatureTestRunner coverage for normal, narrower SIMD tiers, and scalar fallback. - [ ] FeatureTestRunner coverage for normal, narrower SIMD tiers, and scalar fallback.
- [ ] Constrained multi-group allocator coverage with balanced exactly-once returns. - [ ] Constrained multi-group allocator coverage with balanced exactly-once returns.
- [ ] Exact native-plane comparisons against pinned libaom. - [ ] Exact native-plane comparisons against current-main libaom.
- [ ] Established final-presentation comparisons at the target pixel precision. - [ ] Established final-presentation comparisons at the target pixel precision.
- [ ] Scoped StyleCop and vertical-whitespace inspection. - [ ] Scoped StyleCop and vertical-whitespace inspection.
- [ ] No stale unsupported capability claims or removed-code references. - [ ] No stale unsupported capability claims or removed-code references.
- [ ] No restore-source failures, background test hosts, detached processes, or crash-report popups. - [ ] No restore-source failures, background test hosts, detached processes, or crash-report popups.
- [ ] .gitattributes unchanged. - [ ] .gitattributes unchanged.
- [ ] git diff --check clean. - [ ] git diff --check clean.
- [ ] Documentation records exact commands, counts, fixture hashes, and results. - [ ] Documentation records exact commands, counts, current-main reference revision evidence, and results.
- [ ] Commit only after the relevant checkpoint is genuinely complete. - [ ] Commit only after the relevant checkpoint is genuinely complete.
- [ ] Do not push. - [ ] Do not push.

14
src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs

@ -59,6 +59,7 @@ internal class Av1FrameBuffer<T> : IDisposable
/// <param name="sequenceHeader">The sequence header defining maximum dimensions, bit depth, and chroma layout.</param> /// <param name="sequenceHeader">The sequence header defining maximum dimensions, bit depth, and chroma layout.</param>
/// <param name="maxColorFormat">The maximum color format to allocate for a non-monochrome sequence.</param> /// <param name="maxColorFormat">The maximum color format to allocate for a non-monochrome sequence.</param>
/// <param name="is16BitPipeline">Indicates whether reconstruction uses native 16-bit sample storage.</param> /// <param name="is16BitPipeline">Indicates whether reconstruction uses native 16-bit sample storage.</param>
/// <exception cref="InvalidImageContentException">The padded frame planes cannot be represented as contiguous allocations.</exception>
public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHeader, Av1ColorFormat maxColorFormat, bool is16BitPipeline) public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHeader, Av1ColorFormat maxColorFormat, bool is16BitPipeline)
{ {
this.MemoryAllocator = configuration.MemoryAllocator; this.MemoryAllocator = configuration.MemoryAllocator;
@ -107,6 +108,19 @@ internal class Av1FrameBuffer<T> : IDisposable
break; break;
} }
long lumaElementCount = (long)strideY * this.storageElementsPerSample * heightY;
long chromaElementCount = (long)strideChroma * this.storageElementsPerSample * heightChroma;
bool planesExceedContiguousLimit =
lumaElementCount >= int.MaxValue ||
(bufferEnableMask == PictureBufferFullMask && chromaElementCount >= int.MaxValue);
if (planesExceedContiguousLimit)
{
// The reconstruction operators use one span plus a constant stride to address padded neighbors. Reject an
// external geometry that cannot satisfy that ownership contract before Allocate2D falls back to groups.
throw new InvalidImageContentException("The AV1 frame dimensions exceed the contiguous decoder plane limit.");
}
this.BufferY = null; this.BufferY = null;
this.BufferCb = null; this.BufferCb = null;
this.BufferCr = null; this.BufferCr = null;

2
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -1057,7 +1057,7 @@ internal sealed class PngEncoderCore : IDisposable
/// <exception cref="NotSupportedException">CICP matrix coefficients other than Identity are not supported in PNG.</exception> /// <exception cref="NotSupportedException">CICP matrix coefficients other than Identity are not supported in PNG.</exception>
private void WriteCicpChunk(Stream stream, ImageMetadata metaData) private void WriteCicpChunk(Stream stream, ImageMetadata metaData)
{ {
if (metaData.CicpProfile is null) if (this.chunkFilter == PngChunkFilter.ExcludeAll || metaData.CicpProfile is null)
{ {
return; return;
} }

35
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1FrameBufferTests.cs

@ -48,6 +48,33 @@ public class Av1FrameBufferTests
Assert.True(memoryGroup.TotalLength > allocator.BufferCapacityInBytes); Assert.True(memoryGroup.TotalLength > allocator.BufferCapacityInBytes);
} }
/// <summary>
/// Verifies that an external frame geometry cannot make the padded-plane owner fall back to multiple groups.
/// </summary>
[Fact]
public void ConstructorRejectsPaddedPlaneThatCannotBeContiguous()
{
TestMemoryAllocator allocator = new();
allocator.EnableNonThreadSafeLogging();
Configuration configuration = Configuration.Default.Clone();
configuration.MemoryAllocator = allocator;
ObuSequenceHeader sequenceHeader = new()
{
MaxFrameWidth = 65_536,
MaxFrameHeight = 65_536,
ColorConfig = new ObuColorConfig
{
IsMonochrome = true,
BitDepth = Av1BitDepth.EightBit
}
};
Assert.Throws<InvalidImageContentException>(
() => new Av1FrameBuffer<byte>(configuration, sequenceHeader, Av1ColorFormat.Yuv400, false));
Assert.Empty(allocator.AllocationLog);
}
/// <summary> /// <summary>
/// Verifies that a failure while renting the final chroma plane releases every previously rented plane. /// Verifies that a failure while renting the final chroma plane releases every previously rented plane.
/// </summary> /// </summary>
@ -72,7 +99,6 @@ public class Av1FrameBufferTests
// All three padded planes fit in one backing owner each, making attempt three the Cr plane rent after Y and // All three padded planes fit in one backing owner each, making attempt three the Cr plane rent after Y and
// Cb have succeeded. The allocator log therefore contains exactly the two owners requiring rollback. // Cb have succeeded. The allocator log therefore contains exactly the two owners requiring rollback.
Assert.Throws<InvalidMemoryOperationException>( Assert.Throws<InvalidMemoryOperationException>(
() => new Av1FrameBuffer<byte>(configuration, sequenceHeader, Av1ColorFormat.Yuv420, false)); () => new Av1FrameBuffer<byte>(configuration, sequenceHeader, Av1ColorFormat.Yuv420, false));
@ -116,10 +142,11 @@ public class Av1FrameBufferTests
Assert.Throws<InvalidMemoryOperationException>( Assert.Throws<InvalidMemoryOperationException>(
() => new Av1BlockDecoder(sequenceHeader, frameHeader, frameBuffer, loopFilterContext, inverseQuantizer)); () => new Av1BlockDecoder(sequenceHeader, frameHeader, frameBuffer, loopFilterContext, inverseQuantizer));
TestMemoryAllocator.AllocationRequest allocation = Assert.Single(allocator.AllocationLog);
TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog);
Assert.Equal(3, allocator.AllocationAttemptCount); Assert.Equal(3, allocator.AllocationAttemptCount);
Assert.Single(allocator.AllocationLog); Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer);
Assert.Single(allocator.ReturnLog);
Assert.Equal(allocator.AllocationLog[0].HashCodeOfBuffer, allocator.ReturnLog[0].HashCodeOfBuffer);
} }
/// <summary> /// <summary>

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

@ -165,8 +165,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]);
// Pinned libaom's is_nontrans_global_motion rejects only TRANSLATION, so the default identity model omits these // Current libaom's is_nontrans_global_motion rejects only TRANSLATION, so the default identity model omits
// sentinel symbols even though is_global_mv_block uses the separate greater-than-translation classification. // these sentinel symbols even though is_global_mv_block uses the separate greater-than-translation classification.
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();

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

@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp; using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -570,7 +571,7 @@ public class Av1ReconstructionConformanceTests
while (obuOffset < validPayload.Length) while (obuOffset < validPayload.Length)
{ {
byte obuHeader = validPayload[obuOffset]; byte obuHeader = validPayload[obuOffset];
Assert.True((obuHeader & 0x02) != 0); Assert.NotEqual(0, obuHeader & 0x02);
int headerLength = 1 + ((obuHeader >> 2) & 1); int headerLength = 1 + ((obuHeader >> 2) & 1);
int sizeFieldOffset = obuOffset + headerLength; int sizeFieldOffset = obuOffset + headerLength;
@ -3077,8 +3078,8 @@ public class Av1ReconstructionConformanceTests
int planeSampleCount = ProgressiveFixtureWidth * ProgressiveFixtureHeight; int planeSampleCount = ProgressiveFixtureWidth * ProgressiveFixtureHeight;
int frameSampleCount = planeSampleCount * 4; int frameSampleCount = planeSampleCount * 4;
// The pinned reference contains both progressive YUV444-alpha outputs in decode order. Select the second frame // The reference stores both progressive YUV444-alpha outputs in decode order. Select the second frame so this
// so this assertion cannot pass by comparing only the independently decodable base layer. // assertion cannot pass by comparing only the independently decodable base layer.
ReadOnlySpan<byte> nativeReference = referenceBytes; ReadOnlySpan<byte> nativeReference = referenceBytes;
Assert.True(nativeReference.StartsWith(fileHeader)); Assert.True(nativeReference.StartsWith(fileHeader));
nativeReference = nativeReference[fileHeader.Length..]; nativeReference = nativeReference[fileHeader.Length..];
@ -3094,7 +3095,6 @@ public class Av1ReconstructionConformanceTests
// The Y4M stores the color item's Y, U, and V planes before the auxiliary alpha plane. Native AV1 reconstruction // The Y4M stores the color item's Y, U, and V planes before the auxiliary alpha plane. Native AV1 reconstruction
// is compared with exactly those first three planes of the final dependent frame. // is compared with exactly those first three planes of the final dependent frame.
ReadOnlySpan<byte> colorReference = finalFrameReference[..(planeSampleCount * 3)]; ReadOnlySpan<byte> colorReference = finalFrameReference[..(planeSampleCount * 3)];
ReadOnlySpan<byte> alphaReference = finalFrameReference[(planeSampleCount * 3)..];
using Av1Decoder decoder = new(configuration); using Av1Decoder decoder = new(configuration);
using Av1FrameBuffer<byte> frameBuffer = decoder.DecodeFrameBuffer( using Av1FrameBuffer<byte> frameBuffer = decoder.DecodeFrameBuffer(
@ -3155,18 +3155,6 @@ public class Av1ReconstructionConformanceTests
Assert.Equal(ProgressiveFixtureHeight, image.Height); Assert.Equal(ProgressiveFixtureHeight, image.Height);
Assert.Single(image.Frames); Assert.Single(image.Frames);
Assert.Equal(HeifBitDepth.Bit8, image.Metadata.GetHeifMetadata().BitDepth); Assert.Equal(HeifBitDepth.Bit8, image.Metadata.GetHeifMetadata().BitDepth);
// The feature-runner wrapper compares the complete RGBA image through CompareToReferenceOutput. Bind the
// composed alpha channel to the independent native auxiliary plane here as separate codec evidence.
for (int y = 0; y < ProgressiveFixtureHeight; y++)
{
Span<Rgba32> imageRow = image.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y);
ReadOnlySpan<byte> alphaRow = alphaReference.Slice(y * ProgressiveFixtureWidth, ProgressiveFixtureWidth);
for (int x = 0; x < ProgressiveFixtureWidth; x++)
{
Assert.Equal(alphaRow[x], imageRow[x].A);
}
}
} }
/// <summary> /// <summary>
@ -3789,7 +3777,10 @@ public class Av1ReconstructionConformanceTests
FeatureTestRunner.DeserializeForXunit<TestImageProvider<Rgba32>>(providerDump); FeatureTestRunner.DeserializeForXunit<TestImageProvider<Rgba32>>(providerDump);
using Image<Rgba32> image = provider.GetImage(); using Image<Rgba32> image = provider.GetImage();
image.DebugSave(provider);
// CICP records the AVIF source component layout, but PNG permits only the identity matrix. The debug image
// is a pixel artifact; the test verifies source metadata independently where that is part of the contract.
image.DebugSave(provider, new PngEncoder { SkipMetadata = true });
image.CompareToReferenceOutput(ImageComparer.Exact, provider); image.CompareToReferenceOutput(ImageComparer.Exact, provider);
} }
@ -3805,7 +3796,10 @@ public class Av1ReconstructionConformanceTests
using Image<Rgba32> sequence = provider.GetImage(); using Image<Rgba32> sequence = provider.GetImage();
using Image<Rgba32> finalFrame = sequence.Frames.CloneFrame(sequence.Frames.Count - 1); using Image<Rgba32> finalFrame = sequence.Frames.CloneFrame(sequence.Frames.Count - 1);
finalFrame.DebugSave(provider);
// The retained source CICP matrix cannot be represented in a PNG cICP chunk. Omit metadata only from the
// diagnostic output; the exact reference comparison below still consumes the original decoded image.
finalFrame.DebugSave(provider, new PngEncoder { SkipMetadata = true });
finalFrame.CompareToReferenceOutput(ImageComparer.Exact, provider); finalFrame.CompareToReferenceOutput(ImageComparer.Exact, provider);
} }
@ -4042,7 +4036,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 pinned libaom decoder.</param> /// <param name="reference">The planar Y, U, and V samples produced by the current-main libaom decoder.</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,

8
tests/ImageSharp.Tests/Formats/Heif/HeifDecoderTests.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Heif;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -86,7 +87,12 @@ public class HeifDecoderTests
Assert.Null(converted.Metadata.IccProfile); Assert.Null(converted.Metadata.IccProfile);
Assert.NotEmpty(ImageComparer.Exact.CompareImages(preserved, converted)); Assert.NotEmpty(ImageComparer.Exact.CompareImages(preserved, converted));
converted.DebugSave(provider, testOutputDetails: "IccConverted"); // The decoded metadata retains the AVIF source matrix, which PNG cannot represent. The debug output exists
// only to inspect converted pixels, so omit metadata without altering the image under test.
converted.DebugSave(
provider,
new PngEncoder { SkipMetadata = true },
testOutputDetails: "IccConverted");
// The PNG is the independent RGB source used by libavif's avifenc. A tolerant comparison accounts for the // The PNG is the independent RGB source used by libavif's avifenc. A tolerant comparison accounts for the
// AV1 loss while proving the AVIF ICC stage produces the same target-profile interpretation. // AV1 loss while proving the AVIF ICC stage produces the same target-profile interpretation.

Loading…
Cancel
Save