diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 6aee22365f..99052c0d0c 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -338,10 +338,29 @@ Independent loop-filter delta entropy correction, verified on 2026-09-05: `av1/encoder/encodeframe.c:2357`). Existing native output must not be assumed to exercise multi-delta syntax. A complete independently authored multi-delta bitstream remains a verification gap. This correction does not establish encoder parity or complete decoder correctness. No benchmark was run. -- Following the decoded values into deblocking found a further clipping-order discrepancy: - `Av1LoopFilterDecoder.cs:373-390` clips the reference adjustment before adding the mode adjustment; - native `av1/common/av1_loopfilter.c:95-101,195-201` clips their combined result once. A production-frame - regression and correction are still pending for that separately identified numerical defect. + +Loop-filter level clipping correction after checkpoint `77f535828`, verified on 2026-09-05: + +- Following decoded delta values into deblocking found another numerical defect. At the preceding checkpoint, + `Av1LoopFilterDecoder.cs:373-390` clipped the reference adjustment before adding the mode adjustment. + Native `av1/common/av1_loopfilter.c:95-101,182-187` clips their combined result once, both for per-block + delta-LF and for the precomputed frame-level table. Opposite adjustments must be allowed to cancel before clipping. +- The production-frame regression failed before correction: base level 1, reference delta -63, and mode delta +63 + should retain level 1, but premature clipping produced level 63. An expected sample of 100 became 104 at the first + differing position (`loop-level-before.trx`); later edge samples also differed. VSTest stopped on that first failure. + The local runner then failed to print xUnit's Unicode arrows under cp1252. The saved report was inspected and the + runner's stdout encoding corrected; the failed test was not rerun before the implementation change. +- `Av1LoopFilterDecoder.GetFilterLevel` now adds both adjustments using the scale derived from the original level, + then clips once. Existing base/delta-LF and segmentation clipping remain in their normative order. + No allocation, ownership, or syntax policy changed. +- The existing production-frame test and its three expected outputs are retained. Four new cases exercise cancelling + deltas at both limits, with delta-LF present and absent, through `DecodeFrame` and an independent scalar filter. + Final Release net11.0 incremental build reported zero errors and zero warnings; the earlier test compilation + reported 1,009 existing warnings. Roslynk reports zero compiler errors. +- Serialized Visual Studio VSTest passed **11/11** in `loop-level-final.trx` (5.7519 seconds): deblocking scalar/SIMD + definitions, production-frame delta cases, and native deblocking, CDF-update, profile, and all-intra fixtures. + The earlier 2,095-case report applies to the preceding checkpoint. No benchmark or separate-encoder parity + measurement was run, and the complete multi-delta bitstream verification gap remains open. Motion-controller investigation continued after correction checkpoint `578ec34d9`: diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs index 8dbc55869b..47e7208e5d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs @@ -372,10 +372,7 @@ internal sealed class Av1LoopFilterDecoder { int referenceScale = 1 << (level >> 5); Av1ReferenceFrameType referenceFrame = modeInfo.ReferenceFrames[0]; - level = Av1Math.Clip3( - 0, - Av1Constants.MaxLoopFilter, - level + (parameters.ReferenceDeltas[(int)referenceFrame] * referenceScale)); + level += parameters.ReferenceDeltas[(int)referenceFrame] * referenceScale; if (referenceFrame > Av1ReferenceFrameType.Intra) { @@ -384,11 +381,12 @@ internal sealed class Av1LoopFilterDecoder int modeDeltaIndex = modeInfo.YMode is Av1PredictionMode.GlobalMotionVector or Av1PredictionMode.GlobalGlobalMotionVector ? 0 : 1; - level = Av1Math.Clip3( - 0, - Av1Constants.MaxLoopFilter, - level + (parameters.ModeDeltas[modeDeltaIndex] * referenceScale)); + level += parameters.ModeDeltas[modeDeltaIndex] * referenceScale; } + + // Reference and mode adjustments use the same scale and may cancel beyond either limit. + // Clipping the intermediate reference sum would discard part of that cancellation. + level = Av1Math.Clip3(0, Av1Constants.MaxLoopFilter, level); } return level; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs index 24080444e4..df8237eb2c 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs @@ -59,19 +59,53 @@ public class Av1DeblockingFilterTests ValidateInterEdgeAndDeltaDecisions( Av1PredictionMode.GlobalMotionVector, Av1ReferenceFrameType.Last, + 20, + -3, + 4, + false, 17); ValidateInterEdgeAndDeltaDecisions( Av1PredictionMode.NewMotionVector, Av1ReferenceFrameType.Last, + 20, + -3, + 4, + false, 21); ValidateInterEdgeAndDeltaDecisions( Av1PredictionMode.GlobalMotionVector, Av1ReferenceFrameType.Golden, + 20, + 2, + 4, + false, 22); } + /// + /// Verifies that opposite reference and mode adjustments cancel before the final filter-level clamp. + /// + [Theory] + [InlineData(1, -63, 63, false)] + [InlineData(63, 63, -63, false)] + [InlineData(1, -63, 63, true)] + [InlineData(63, 63, -63, true)] + public void DecodeFrameCombinesDeltasBeforeClipping(int baseLevel, int referenceDelta, int modeDelta, bool deltaLoopFilterPresent) + { + // Both native paths, per-block delta-LF and the precomputed frame table, clamp only after adding + // reference and mode adjustments. These equal and opposite deltas leave the base level unchanged. + ValidateInterEdgeAndDeltaDecisions( + Av1PredictionMode.NewMotionVector, + Av1ReferenceFrameType.Last, + baseLevel, + referenceDelta, + modeDelta, + deltaLoopFilterPresent, + baseLevel); + } + /// /// Exercises mixed flatness, high-edge-variance, disabled-mask, direction, and bit-depth cases. /// @@ -109,12 +143,15 @@ public class Av1DeblockingFilterTests private static void ValidateInterEdgeAndDeltaDecisions( Av1PredictionMode mode, Av1ReferenceFrameType referenceFrame, + int baseLevel, + int referenceDelta, + int modeDelta, + bool deltaLoopFilterPresent, int expectedLevel) { const int width = Stride; const int height = 8; const int edge = 16; - const int baseLevel = 20; ObuSequenceHeader sequenceHeader = new() { MaxFrameWidth = width, @@ -140,11 +177,12 @@ public class Av1DeblockingFilterTests }; ObuLoopFilterParameters filterParameters = frameHeader.LoopFilterParameters; + frameHeader.DeltaQParameters.IsPresent = deltaLoopFilterPresent; + frameHeader.DeltaLoopFilterParameters.IsPresent = deltaLoopFilterPresent; filterParameters.FilterLevel[0] = baseLevel; filterParameters.ReferenceDeltaModeEnabled = true; - filterParameters.ReferenceDeltas[(int)Av1ReferenceFrameType.Last] = -3; - filterParameters.ReferenceDeltas[(int)Av1ReferenceFrameType.Golden] = 2; - filterParameters.ModeDeltas[1] = 4; + filterParameters.ReferenceDeltas[(int)referenceFrame] = referenceDelta; + filterParameters.ModeDeltas[1] = modeDelta; using Av1FrameBuffer frameBuffer = new( Configuration.Default,