diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index e7cd65e81..6f7989b14 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -30,8 +30,8 @@ Reference checkout evidence on 2026-08-31: 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 inter-frame decoder has verified checkpoints through non-translational global prediction. Inter - deblocking decisions and reference/mode deltas remain open as the next ordered checkpoint. +- [x] The inter-frame decoder has verified checkpoints through inter deblocking decisions and + reference/mode deltas. - [~] 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. - [ ] The public AV1 encoder is not implemented. HeifEncoderCore.Encode throws NotSupportedException when AV1 is selected. @@ -186,7 +186,7 @@ The single-reference syntax, buffer, reconstruction, and ownership foundation is - [x] Scaled-reference prediction. - [x] Local warped prediction. - [x] Non-translational global prediction. -- [~] Inter deblocking decisions and reference/mode deltas. Current item. +- [x] Inter deblocking decisions and reference/mode deltas. Verified equal-average compound checkpoint evidence on 2026-08-31: @@ -502,6 +502,45 @@ Verified non-translational global-prediction checkpoint evidence on 2026-08-31: - [x] The focused Release checkpoint set passes 11/11 on net10.0 and 11/11 on net11.0, with zero failures or skips. Scoped analyzer verification passes for all six changed C# files. Roslynk reports zero compiler errors, `git diff --check` passes, and `.gitattributes` is unchanged. +- [x] The completed checkpoint was committed as `25295683d39a2336e9b98484c9fd54f33107ea66` + with author and committer `James Jackson-South `. + +Verified inter-deblocking checkpoint evidence on 2026-08-31: + +- [x] Audited frame-level loop-filter syntax and primary-reference inheritance against + `setup_loopfilter` in current `av1/decoder/decodeframe.c`; per-superblock delta-LF parsing and + prediction against `read_delta_q_params` in `av1/decoder/decodemv.c`; and default reference/mode + deltas against `av1/common/entropymode.c` at observed current-main revision + `441c439b9916474cac15d2822af47a9ad70674a8`. +- [x] Audited filter-level derivation, segmentation adjustment, reference scaling, global/non-global + mode classes, skipped-transform prediction-unit decisions, transform-edge selection, kernel length, + sharpness limits, and vertical-then-horizontal traversal against `get_filter_level`, + `set_lpf_parameters`, `av1_filter_block_plane_vert`, `av1_filter_block_plane_horz`, and + `av1_thread_loop_filter_rows`. No production arithmetic change was required. +- [x] Added direct production `Av1LoopFilterDecoder.DecodeFrame()` coverage using adjacent skipped + 16x8 inter blocks split into 8x8 transforms. An independent scalar oracle proves that internal + transform edges remain untouched and the prediction-unit edge uses current-libaom levels 17 for + LAST/GLOBALMV, 21 for LAST/NEWMV, and 22 for GOLDEN/GLOBALMV. Existing `FeatureTestRunner` + coverage continues to verify every filter width at 8, 10, and 12 bits under intrinsic and scalar + dispatch. +- [x] Current official libaom decoded the retained 20,750-byte 8-bit, 37,169-byte 10-bit, and + 23,769-byte 12-bit elementary streams with one thread, row threading disabled, raw output, and their + native output depths. The generated native files match the retained references byte for byte. Their + output SHA-256 values are + `8DDE2EEC742C39F0579C29AE84CBA0FE01522A9008ADCB2CFFCCEC0295D18141`, + `9A59DD92A0C579F942ACCA8281EBD0465DC848BE200A4D2FF57EAFF589445F6C`, and + `EF712BE32AF7CF0A95C5C41BDCC51AFC05A4AB7C047383F5F65EDAD2BB986712`. +- [x] Reused the already current-main scaled-reference sequence as the real inter checkpoint. It + requires an inter frame with reference/mode-delta processing enabled, nonzero chroma filter levels, + intra, inter, and skipped-inter blocks; compares both decoded native frames exactly; compares final + presentation through ImageSharp's established reference-output API; and passes constrained tracked + allocation with balanced returns. +- [x] Removed an obsolete SVT-AV1 design link from mode-map documentation. Current official libaom + remains the sole external codec implementation source. +- [x] The focused Release checkpoint set passes 6/6 on net10.0 and 6/6 on net11.0, with zero failures + or skips. +- [x] Scoped analyzer verification passes for all four changed C# files. Roslynk reports zero compiler + errors, `git diff --check` passes, and `.gitattributes` is unchanged. For every item: @@ -517,7 +556,7 @@ 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. -- [~] 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. +- [~] 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. Current item. - [~] 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-block copy has exact native reconstruction and feature-isolated SIMD evidence. diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs index 97427b20f..54d338316 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs @@ -11,9 +11,6 @@ internal partial class Av1FrameInfo /// /// Mapping of instances, from position to index into the . /// - /// - /// For a visual representation of how this map looks in practice, see - /// public class Av1FrameModeInfoMap { /// diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs index c574b4b94..b27c8ae63 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs @@ -1,8 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -46,6 +50,28 @@ public class Av1DeblockingFilterTests public void FilterMatchesIndependentDefinitionAcrossIntrinsicTiers() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateFilters, Configurations); + /// + /// Verifies skipped inter-edge decisions and reference and mode level deltas through the production frame filter. + /// + [Fact] + public void DecodeFrameMatchesCurrentLibaomInterEdgeAndDeltaDecisions() + { + ValidateInterEdgeAndDeltaDecisions( + Av1PredictionMode.GlobalMotionVector, + Av1ReferenceFrameType.Last, + 17); + + ValidateInterEdgeAndDeltaDecisions( + Av1PredictionMode.NewMotionVector, + Av1ReferenceFrameType.Last, + 21); + + ValidateInterEdgeAndDeltaDecisions( + Av1PredictionMode.GlobalMotionVector, + Av1ReferenceFrameType.Golden, + 22); + } + /// /// Exercises mixed flatness, high-edge-variance, disabled-mask, direction, and bit-depth cases. /// @@ -77,6 +103,109 @@ public class Av1DeblockingFilterTests } } + /// + /// Filters two adjacent skipped inter blocks and compares the visible luma plane with the scalar definition. + /// + private static void ValidateInterEdgeAndDeltaDecisions( + Av1PredictionMode mode, + Av1ReferenceFrameType referenceFrame, + int expectedLevel) + { + const int width = Stride; + const int height = 8; + const int edge = 16; + const int baseLevel = 20; + ObuSequenceHeader sequenceHeader = new() + { + MaxFrameWidth = width, + MaxFrameHeight = height, + Use128x128Superblock = false, + ColorConfig = new ObuColorConfig + { + IsMonochrome = true, + BitDepth = Av1BitDepth.EightBit, + }, + }; + + ObuFrameHeader frameHeader = new() + { + FrameType = ObuFrameType.InterFrame, + ModeInfoColumnCount = width >> Av1Constants.ModeInfoSizeLog2, + ModeInfoRowCount = height >> Av1Constants.ModeInfoSizeLog2, + FrameSize = new ObuFrameSize + { + FrameWidth = width, + FrameHeight = height, + }, + }; + + ObuLoopFilterParameters filterParameters = frameHeader.LoopFilterParameters; + filterParameters.FilterLevel[0] = baseLevel; + filterParameters.ReferenceDeltaModeEnabled = true; + filterParameters.ReferenceDeltas[(int)Av1ReferenceFrameType.Last] = -3; + filterParameters.ReferenceDeltas[(int)Av1ReferenceFrameType.Golden] = 2; + filterParameters.ModeDeltas[1] = 4; + + using Av1FrameBuffer frameBuffer = new( + Configuration.Default, + sequenceHeader, + Av1ColorFormat.Yuv400, + false); + + using Av1FrameInfo frameInfo = new(sequenceHeader); + Av1SuperblockInfo superblock = frameInfo.GetSuperblock(Point.Empty); + Av1BlockModeInfo leftModeInfo = new(Av1BlockSize.Block16x8, Point.Empty) + { + Skip = true, + YMode = mode, + }; + + Av1BlockModeInfo rightModeInfo = new(Av1BlockSize.Block16x8, new Point(4, 0)) + { + Skip = true, + YMode = mode, + }; + + leftModeInfo.ReferenceFrames[0] = referenceFrame; + rightModeInfo.ReferenceFrames[0] = referenceFrame; + frameInfo.UpdateModeInfo(leftModeInfo, superblock); + frameInfo.UpdateModeInfo(rightModeInfo, superblock); + superblock.BlockCount = 2; + + Av1LoopFilterContext loopFilterContext = new(sequenceHeader); + loopFilterContext.SetTransformSize(Av1Plane.Y, Point.Empty, Av1TransformSize.Size8x8); + loopFilterContext.SetTransformSize(Av1Plane.Y, new Point(2, 0), Av1TransformSize.Size8x8); + loopFilterContext.SetTransformSize(Av1Plane.Y, new Point(4, 0), Av1TransformSize.Size8x8); + loopFilterContext.SetTransformSize(Av1Plane.Y, new Point(6, 0), Av1TransformSize.Size8x8); + + byte[] expected = new byte[width * height]; + for (int row = 0; row < height; row++) + { + Span expectedRow = expected.AsSpan(row * width, width); + Span actualRow = frameBuffer.DeriveBlockPointer(Av1Plane.Y, 0, 0).DangerousGetRowSpan(row); + expectedRow[..edge].Fill(100); + expectedRow[edge..].Fill(130); + actualRow[..edge].Fill(100); + actualRow[edge..].Fill(130); + } + + int limit = expectedLevel; + int boundaryLimit = (2 * (expectedLevel + 2)) + limit; + int highEdgeVarianceThreshold = expectedLevel >> 4; + ApplyReference(expected, true, edge, 8, limit, boundaryLimit, highEdgeVarianceThreshold, 8); + ApplyReference(expected, true, (4 * width) + edge, 8, limit, boundaryLimit, highEdgeVarianceThreshold, 8); + + Av1LoopFilterDecoder decoder = new(sequenceHeader, frameHeader, frameInfo, frameBuffer, loopFilterContext); + decoder.DecodeFrame(); + + for (int row = 0; row < height; row++) + { + ReadOnlySpan expectedRow = expected.AsSpan(row * width, width); + ReadOnlySpan actualRow = frameBuffer.DeriveBlockPointer(Av1Plane.Y, 0, 0).DangerousGetRowSpan(row); + Assert.Equal(expectedRow, actualRow); + } + } + /// /// Verifies one eight-bit filter configuration against the independent definition. /// diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs index 20cf55e73..14de4b743 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs @@ -421,7 +421,7 @@ public class Av1ReconstructionConformanceTests /// and twelve-bit AV1 and AVIF content. /// [Fact] - public void DecodeMatchesPinnedLibaomReference() + public void DecodeDeblockingFixturesMatchCurrentLibaomReference() { ValidateFixture( TestImages.Heif.Av1Deblocking8BitAvif, @@ -3389,7 +3389,7 @@ public class Av1ReconstructionConformanceTests /// /// The complete AVIF container. /// The AV1 elementary-stream sample extracted from the container. - /// The native planar output produced by the pinned libaom decoder. + /// The retained native planar output. /// The expected displayed width. /// The expected displayed height. /// The expected AV1 sample precision. @@ -3413,7 +3413,7 @@ public class Av1ReconstructionConformanceTests /// Validates complete native-plane reconstruction for one AV1 elementary-stream sample. /// /// The AV1 elementary-stream sample. - /// The native planar output produced by the pinned libaom decoder. + /// The retained native planar output. /// The expected reconstructed width. /// The expected reconstructed height. /// The expected AV1 sample precision. diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 825d1a289..4b798a2c7 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1308,8 +1308,8 @@ public static class TestImages public const string Animated8BitWithAlphaExifXmp = "Heif/colors-animated-8bpc-alpha-exif-xmp.avif"; public const string Animated12BitWithKeyframes = "Heif/colors-animated-12bpc-keyframes-0-2-3.avif"; - // Genuine libavif AVIF inputs, independently encoded AV1 samples, and exact native-plane output from the - // pinned scalar libaom reference decoder. See the fixture README for source and encoding details. + // Genuine AVIF inputs, independently encoded AV1 samples, and retained exact native-plane output. The + // fixture README records provenance and the current-main verification status for each checkpoint. public const string Av1Deblocking8BitAvif = "Heif/Av1/Conformance/libavif-kodim23-8b.avif"; public const string Av1Deblocking8BitPayload = "Heif/Av1/Conformance/libavif-kodim23-8b.bit"; public const string Av1Deblocking8BitReference = "Heif/Av1/Conformance/libavif-kodim23-8b-libaom.yuv"; diff --git a/tests/Images/Input/Heif/Av1/Conformance/README.md b/tests/Images/Input/Heif/Av1/Conformance/README.md index 352ca28e6..ecdc3f6d1 100644 --- a/tests/Images/Input/Heif/Av1/Conformance/README.md +++ b/tests/Images/Input/Heif/Av1/Conformance/README.md @@ -6,12 +6,15 @@ These fixtures provide independent reference output for AV1 reconstruction and A The source images and original AVIF files come from `libavif/tests/data` at commit `062e582e8afda88e6baf988fdcf046a801efa0f5`. Their licenses are recorded in libavif's `tests/data/README.md` and continue to apply to the derived fixtures. This includes the unrestricted Kodak image, the CC BY 3.0 Cosmos Laundromat frame, and files distributed under libavif's BSD-2-Clause license. -Reference files were generated with scalar builds of: +Original fixture-generation records name scalar builds of: - libaom commit `03087864cf4bea6abb0d28f95cf7843511413d8f`; - libavif 1.4.2 from commit `062e582e8afda88e6baf988fdcf046a801efa0f5`, linked to that libaom build. -The reference builds use `AOM_TARGET_CPU=generic` and disable libyuv. Native reconstruction therefore comes from libaom, and AVIF presentation comes from libavif's own conversion path, without architecture-specific SIMD or ImageSharp code. +Those revisions describe how the retained assets were originally produced; they do not pin the current +verification checkout. Current AV1 algorithm, arithmetic, syntax, and native-output verification uses only +the clean official libaom `main` checkout. Libavif commands record container and presentation provenance +only and are not used as an AV1 implementation reference. ## File conventions @@ -52,6 +55,36 @@ The reference builds use `AOM_TARGET_CPU=generic` and disable libyuv. Native rec The corresponding tests also assert the syntax required by each family before comparing output. This prevents an inactive tool or an incorrectly substituted stream from passing solely because its final pixels happen to match. +## Baseline deblocking fixtures + +On 2026-08-31 current official libaom `main` at observed revision +`441c439b9916474cac15d2822af47a9ad70674a8` decoded the baseline 8-, 10-, and 12-bit +elementary streams using one thread, row threading disabled, raw output, and the corresponding native +output depth: + +```text +aomdec --codec=av1 --threads=1 --row-mt=0 --rawvideo --output-bit-depth=8 -o deblocking-8b.yuv libavif-kodim23-8b.bit +aomdec --codec=av1 --threads=1 --row-mt=0 --rawvideo --output-bit-depth=10 -o deblocking-10b.yuv libavif-cosmos1650-10b.bit +aomdec --codec=av1 --threads=1 --row-mt=0 --rawvideo --output-bit-depth=12 -o deblocking-12b.yuv libaom-cosmos1650-12b.bit +``` + +The payloads contain 20,750, 37,169, and 23,769 bytes. Their SHA-256 values are +`B7B1D3F85A870475ACF579FBB7A0B59FF94C30F84B3F3A066411A50F9BA1BD20`, +`F930BF11EB2F61BF4EE0FE61853CD387A4C3DBE53503707DB7B2C59D9D273FF3`, and +`A45F9653255C1A660906554BAFCC13FDEE04F88CC6D0E08E1EFA169CF0C6DDD2`. The generated +native outputs contain 589,824, 2,629,632, and 2,629,632 bytes and match the retained references exactly. +Their SHA-256 values are +`8DDE2EEC742C39F0579C29AE84CBA0FE01522A9008ADCB2CFFCCEC0295D18141`, +`9A59DD92A0C579F942ACCA8281EBD0465DC848BE200A4D2FF57EAFF589445F6C`, and +`EF712BE32AF7CF0A95C5C41BDCC51AFC05A4AB7C047383F5F65EDAD2BB986712`. + +The focused production test covers active 8-, 10-, and 12-bit deblocking. A separate current-main +dependent sequence exercises inter, intra, and skipped-inter blocks with reference/mode deltas enabled, +compares native reconstruction exactly, compares final presentation through the established reference-output +API, and repeats under constrained tracked allocation. The direct production filter test independently +distinguishes global and non-global mode-delta classes, LAST and GOLDEN reference deltas, internal transform +edges, and skipped prediction-unit boundaries. + ## Official ten-bit sequence fixtures The `libaom-av1-1-b10-23-film-grain-50.ivf` and `libaom-av1-1-b10-24-monochrome.ivf` streams are the official files from libaom's test-data bucket. Their SHA-1 values are `2F883C7E11C21A31F79BD9C809541BE90B0C7C4A` and `03A8D002594CCC51932332002BB6F9837EF46D0F`, exactly matching `test/test-data.sha1` at pinned libaom commit `03087864cf4bea6abb0d28f95cf7843511413d8f`. Their SHA-256 values are `C36CF5AB6A2E9E27C212C06863759B60791E3FA681A0800B5D57FD4192EF29CB` and `6A1B0729305A167F10737A5375F0570139F055BCD7916DF260B653AB2210ADC1`.