diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md
index 1e532cc6c..0b3a418f1 100644
--- a/HEIF_IMPLEMENTATION_PLAN.md
+++ b/HEIF_IMPLEMENTATION_PLAN.md
@@ -29,7 +29,7 @@ Checkboxes may be marked complete only when the implementation and the verificat
## Delivery dashboard
-Last reconciled with the source tree on 2026-08-29 against production checkpoint `a5e4cc6cc6cf87ee02b41cfe4ca71d1ef3411ed7`. Committed checkpoints include the AV1 transform architecture, OBU framing, intra-block copy, official extreme-displacement intra-block-copy conformance, the 12-profile reconstruction matrix, corrected twelve-bit inverse-transform SIMD arithmetic, layered-item properties, layered reference/header/CDF/motion-field state, inter-frame intra blocks, SIMD-first translational prediction, complete single-reference inter reconstruction, compound reference trees and modes, paired reference-MV derivation, reference-dependent bounded sequence decoding, allocation-free SIMD-first equal averaging, selected inter-intra prediction, selectable compound blending, OBMC, scaled-reference reconstruction, local warped prediction, non-translational global prediction, official motion-vector conformance, official spatial- and temporal-layer default-operating-point conformance, selected spatial-layer presentation, and progressive color and auxiliary-alpha conformance. This dashboard is the authoritative delivery order. The detailed phase checklists below provide subsystem evidence; they do not override the current-stage marker or permit work to skip ahead.
+Last reconciled with the source tree on 2026-08-29 against production checkpoint `03737411e62155df8e998c37475ceeebf4f053bf`. Committed checkpoints include the AV1 transform architecture, OBU framing, intra-block copy, official extreme-displacement intra-block-copy conformance, the 12-profile reconstruction matrix, corrected twelve-bit inverse-transform SIMD arithmetic, layered-item properties, layered reference/header/CDF/motion-field state, inter-frame intra blocks, SIMD-first translational prediction, complete single-reference inter reconstruction, compound reference trees and modes, paired reference-MV derivation, reference-dependent bounded sequence decoding, allocation-free SIMD-first equal averaging, selected inter-intra prediction, selectable compound blending, OBMC, scaled-reference reconstruction, local warped prediction, non-translational global prediction, official motion-vector conformance, official spatial- and temporal-layer default-operating-point conformance, selected spatial-layer presentation, and progressive color and auxiliary-alpha conformance. This dashboard is the authoritative delivery order. The detailed phase checklists below provide subsystem evidence; they do not override the current-stage marker or permit work to skip ahead.
Commit `1c58d855f70b024170ced9eb0a7005f0f9c955ad` records the complete official motion-vector conformance checkpoint. The official IVF has SHA-1 `F064290D7FCD3B3DE19020E8AEC6C43C88D3A505`, matching the pinned libaom test-data manifest, and SHA-256 `222A9050059B254DAB17CFB802FF829C778E3F93AF18961A622C8268576C1395`; its pinned-libaom Y4M has SHA-256 `D97AC78C81782CF1507549368047769DC677DBE205706458D1EE9C807DE6EC78`. Both source targets build with zero warnings and errors, the `net10.0` test-project analyzer build completes with zero errors and 1,014 pre-existing repository warnings, Roslynk reports zero compiler errors, 3,985 focused `net10.0` cases pass without failures or skips, and `git diff --check` is clean.
@@ -47,6 +47,8 @@ Commit `27ebcd54dc732aff8c665ee1ab61e72ed9621b42` records the official two-spati
Commit `a5e4cc6cc6cf87ee02b41cfe4ca71d1ef3411ed7` records the official one-spatial-layer, two-temporal-layer L1T2 checkpoint. The manifest-matching 48,883-byte IVF and pinned-libaom Y4M prove all eight 640x360 YUV420 frames exactly under normal and scalar dispatch; constrained tracked allocation verifies balanced exactly-once returns. The existing decoder required no production correction. The scoped test-project analyzer build completes with zero errors and 1,011 repository warnings, Roslynk reports zero compiler errors, both focused cases pass without failures or skips, and `git diff --check` is clean.
+Commit `03737411e62155df8e998c37475ceeebf4f053bf` records the official two-spatial-layer, two-temporal-layer L2T2 checkpoint. The manifest-matching 119,907-byte IVF and pinned-libaom Y4M prove all eight 1280x720 YUV420 frames exactly under normal and scalar dispatch; constrained tracked allocation verifies balanced exactly-once returns. The existing decoder required no production correction. The scoped test-project analyzer build completes with zero errors and 1,011 repository warnings, Roslynk reports zero compiler errors, both focused cases pass without failures or skips, and `git diff --check` is clean.
+
Status meanings:
- **Complete:** the implementation and its phase exit evidence are recorded.
@@ -57,7 +59,7 @@ Status meanings:
Current development stage: **Stage 3 — complete AV1 still-image decoding.** The decoder retains reference/header/CDF/motion-field state, derives frame-level skip-mode references, consumes temporal segment prediction, decodes intra-coded blocks inside inter frames, and reconstructs translational single-reference, compound, inter-intra, OBMC, scaled-reference, local warped, and non-translational global prediction before residual traversal. Commit `1c58d855f70b024170ced9eb0a7005f0f9c955ad` adds exact official four-frame coverage through all ordinary inter modes, all three motion modes, every regular/smooth/sharp dual-filter pair, sub-8x8 chroma prediction, and no-round compound intermediates. Commit `019ac5648b4380a6ff4865072c96fde29ce09fad` adds exact selected-layer native reconstruction and scaled presentation. Neither AV1 nor HEVC production encoding is implemented.
-Immediate checkpoint: **verification complete; commit pending — official two-spatial-layer, two-temporal-layer L2T2 AV1 sequence.** All eight 1280x720 YUV420 frames match pinned libaom exactly under normal/scalar dispatch, and constrained tracked allocation returns every allocation exactly once. The existing decoder required no production correction. The scoped test-project analyzer build completes with zero errors and 1,011 repository warnings, Roslynk reports zero compiler errors, both focused cases pass without failures or skips, and `git diff --check` is clean. Only the checkpoint commit remains open.
+Immediate checkpoint: **verification complete; commit pending — official ten-frame active-film-grain AV1 sequence.** All ten displayed 352x288 YUV420 frames match the pinned-libaom grained output exactly under normal and scalar dispatch, active synthesis is explicit, and constrained tracked allocation returns every allocation exactly once. The fixture exposed a swapped AV1 default loop-filter delta: Golden now defaults to -1 and Backward to 0, restoring normative dependent-frame deblocking before display-only grain synthesis. A direct default-table regression, adjacent deblocking/CDEF cases, retained-reference and presentation ownership, both zero-warning Release source builds, the zero-error test-project analyzer build with 1,011 repository warnings, zero Roslynk compiler errors, 28 focused cases, documentation review, and `git diff --check` pass. Only the checkpoint commit remains open.
| Order | Delivery stage | State | Delivered state | Gate that remains open |
| --- | --- | --- | --- | --- |
@@ -610,6 +612,7 @@ Implement and verify in dependency order:
- [x] Decode the official two-spatial-layer L2T1 sequence at its default operating point. The 159,037-byte IVF has manifest-matching SHA-1 `E14825F50FF845B8A6932C64CB254007A0B5E3A1` and SHA-256 `D65824E8E7C19BC958DB810D728B2132B513B5A93CB5F99CA3D7995E19A226E1`; its pinned-libaom Y4M has SHA-256 `7A427631ECBF144F435AA4612F1201415FB1A9BCF9A67BA010AEF830B0C3AB81`. All eight 1280x720 YUV420 frames match exactly under normal and scalar dispatch, and an 8 KiB constrained tracked allocator verifies balanced exactly-once returns. The existing decoder required no production correction. Both Release source targets build with zero warnings and errors, the scoped test-project analyzer build completes with zero errors and 1,011 repository warnings, Roslynk reports zero compiler errors, and all five focused compact-sequence cases pass without failures or skips.
- [x] Decode the official one-spatial-layer, two-temporal-layer L1T2 sequence at its default operating point. The 48,883-byte IVF has manifest-matching SHA-1 `E94687EB0E90179B3800B6D5E11EB7E9BFB34EEC` and SHA-256 `EADE696AB60415D476E1D2A489EC2CCCD24D5EAAADFA4559C6488B7932274C5E`; its pinned-libaom Y4M has SHA-256 `4012DE2D4AFD095E7BB68EAE18B50B0674781BB4971CECABC0E5471E63373ED3`. All eight 640x360 YUV420 frames match exactly under normal/scalar dispatch, and an 8 KiB constrained tracked allocator verifies balanced exactly-once returns. The existing decoder required no production correction. The scoped test-project analyzer build completes with zero errors and 1,011 repository warnings, Roslynk reports zero compiler errors, and both focused cases pass without failures or skips.
- [x] Decode the official two-spatial-layer, two-temporal-layer L2T2 sequence at its default operating point. The 119,907-byte IVF has manifest-matching SHA-1 `32EF2F14EE9CB11A24A22934F4C065E926E5D236` and SHA-256 `14257D5FD3901581DBEB88133378CD79D50EA32506ADB729909AB56B4A52AF3C`; its pinned-libaom Y4M has SHA-256 `1ABB981CFF76BA9557DA437B258D8A95FCA755DED8E3949D857E8388AB1D6AE3`. All eight 1280x720 YUV420 frames match exactly under normal/scalar dispatch, and an 8 KiB constrained tracked allocator verifies balanced exactly-once returns. The existing decoder required no production correction. The scoped test-project analyzer build completes with zero errors and 1,011 repository warnings, Roslynk reports zero compiler errors, and both focused cases pass without failures or skips.
+ - [ ] **Verification complete; commit pending:** decode the official ten-frame active-film-grain sequence. The 28,634-byte IVF has manifest-matching SHA-1 `11BB40026103182C23A88133EDAFCA369E5575E2` and SHA-256 `BA3EDD82A58414F009E1C821B947EC8DE4E0420F33803CA7BFEA39AF6AEEA155`; its pinned-libaom grained Y4M has SHA-256 `C71958DE496A949D12E819190F240BD56010E5AD57D8A0D55903BF380A9E0C26`, while pinned libaom's skip-grain output differs with SHA-256 `9E0762A82E582E854AFF2DC61EBA1E48C0F1D41BBC21E5C896573DA3BB2F1200`. All ten displayed 352x288 YUV420 frames match exactly under normal/scalar dispatch; active synthesis, dependent retained-reference reconstruction, and 2 KiB constrained tracked allocation with exactly-once returns pass. The fixture corrected the AV1 default loop-filter reference deltas from a swapped Golden/Backward pair to the normative `[1, 0, 0, 0, -1, 0, -1, -1]` order. Both Release source targets build with zero warnings and errors, the scoped test-project analyzer build completes with zero errors and 1,011 repository warnings, Roslynk reports zero compiler errors, 28 focused cases pass without failures or skips, documentation review is complete, and `git diff --check` is clean.
- [ ] Lossless and high-bit-depth reconstruction with correct clipping and intermediate precision.
- [x] Route lossless 4x4 blocks through allocation-free reversible inverse Walsh-Hadamard reconstruction for 8/10/12-bit samples, including the DC-only specialization, `Vector128` production traversal, scalar fallback, exact clipping, and `FeatureTestRunner` parity.
- [x] Verify lossless syntax, inverse quantization, prediction, and presented reconstruction with independently encoded 8/10/12-bit AVIF fixtures. The tests require coded residuals with palette and intra-block copy disabled, compare every native YUV sample with the pinned generic libaom-backed decoder, and compare every presented RGBA byte with pinned generic libavif exactly under normal hardware dispatch and the scalar fallback.
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs
index c334384b0..e50527d89 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs
@@ -13,7 +13,8 @@ internal class ObuLoopFilterParameters
///
public ObuLoopFilterParameters()
{
- this.ReferenceDeltas = [1, 0, 0, 0, 0, -1, -1, -1];
+ // AV1 indexes this table from Intra through Alternate. Golden is -1; Backward remains 0.
+ this.ReferenceDeltas = [1, 0, 0, 0, -1, 0, -1, -1];
this.ModeDeltas = [0, 0];
}
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs
index e6027f454..c574b4b94 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter;
using SixLabors.ImageSharp.Tests.TestUtilities;
@@ -27,6 +28,17 @@ public class Av1DeblockingFilterTests
///
private const int EdgeCoordinate = 12;
+ ///
+ /// Verifies the AV1 reference-category default deltas used to derive frame-edge filter levels.
+ ///
+ [Fact]
+ public void LoopFilterReferenceDeltasMatchAv1Defaults()
+ {
+ ObuLoopFilterParameters parameters = new();
+
+ Assert.Equal([1, 0, 0, 0, -1, 0, -1, -1], parameters.ReferenceDeltas);
+ }
+
///
/// Verifies exact filtering and untouched padding against an independent scalar definition.
///
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
index 547d0f38f..a4831e3c9 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
@@ -231,6 +231,11 @@ public class Av1ReconstructionConformanceTests
///
private const int OfficialSpatialTemporalLayerFixtureFrameCount = 8;
+ ///
+ /// The number of frames in the official libaom active-film-grain sequence.
+ ///
+ private const int OfficialFilmGrainFixtureFrameCount = 10;
+
///
/// The coverage bit representing tile-local adaptive CDF updates.
///
@@ -246,6 +251,11 @@ public class Av1ReconstructionConformanceTests
///
private const int ReferenceFrameMotionVectorCoverage = 1 << 2;
+ ///
+ /// The coverage bit representing displayed film-grain synthesis.
+ ///
+ private const int FilmGrainCoverage = 1 << 3;
+
///
/// The bit mask containing every intra prediction mode.
///
@@ -1414,6 +1424,57 @@ public class Av1ReconstructionConformanceTests
OfficialSpatialTemporalLayerFixtureWidth,
OfficialSpatialTemporalLayerFixtureHeight);
+ ///
+ /// Verifies active film-grain presentation and dependent-frame reconstruction against exact pinned-libaom native
+ /// output under normal and scalar dispatch.
+ ///
+ [Fact]
+ public void DecodeOfficialFilmGrainSequenceMatchesPinnedLibaomReference()
+ => FeatureTestRunner.RunWithHwIntrinsicsFeature(
+ ValidateOfficialFilmGrainFixture,
+ ReconstructionConfigurations);
+
+ ///
+ /// Verifies the official film-grain sequence through constrained tracked allocation.
+ ///
+ [Fact]
+ [ValidateDisposedMemoryAllocations]
+ public void DecodeOfficialFilmGrainSequenceWithConstrainedAllocator()
+ {
+ TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 };
+ allocator.EnableNonThreadSafeLogging();
+ Configuration configuration = Configuration.Default.Clone();
+ configuration.MemoryAllocator = allocator;
+
+ int coverage = ValidateOfficialCompactSequence(
+ configuration,
+ TestImages.Heif.Av1OfficialFilmGrainSequence,
+ TestImages.Heif.Av1OfficialFilmGrainSequenceNativeReference,
+ OfficialFilmGrainFixtureFrameCount);
+
+ Assert.NotEqual(0, coverage & FilmGrainCoverage);
+ Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count);
+ Assert.All(
+ allocator.AllocationLog,
+ allocation => Assert.Single(
+ allocator.ReturnLog,
+ returned => returned.AllocationId == allocation.AllocationId));
+ }
+
+ ///
+ /// Decodes the official film-grain sequence and verifies that synthesis is active.
+ ///
+ private static void ValidateOfficialFilmGrainFixture()
+ {
+ int coverage = ValidateOfficialCompactSequence(
+ Configuration.Default,
+ TestImages.Heif.Av1OfficialFilmGrainSequence,
+ TestImages.Heif.Av1OfficialFilmGrainSequenceNativeReference,
+ OfficialFilmGrainFixtureFrameCount);
+
+ Assert.NotEqual(0, coverage & FilmGrainCoverage);
+ }
+
///
/// Decodes one compact official IVF sequence, compares every native sample, and returns its active frame-state
/// coverage mask.
@@ -1472,7 +1533,8 @@ public class Av1ReconstructionConformanceTests
AssertNativePlanesEqual(
decoder,
frameBuffer,
- nativeReference.AsSpan(nativeOffset, nativeFrameLength));
+ nativeReference.AsSpan(nativeOffset, nativeFrameLength),
+ frameIndex);
nativeOffset += nativeFrameLength;
@@ -1480,6 +1542,7 @@ public class Av1ReconstructionConformanceTests
coverage |= frameHeader.DisableCdfUpdate ? 0 : TileCdfUpdateCoverage;
coverage |= frameHeader.DisableFrameEndUpdateCdf ? 0 : FrameEndCdfUpdateCoverage;
coverage |= frameHeader.UseReferenceFrameMotionVectors ? ReferenceFrameMotionVectorCoverage : 0;
+ coverage |= frameHeader.FilmGrainParameters.ApplyGrain ? FilmGrainCoverage : 0;
}
Assert.Equal(ivf.Length, ivfOffset);
@@ -3766,7 +3829,12 @@ public class Av1ReconstructionConformanceTests
/// The decoder state used to identify the coded block containing a mismatch.
/// The reconstructed AV1 component planes.
/// The planar Y, U, and V samples produced by the pinned libaom decoder.
- private static void AssertNativePlanesEqual(Av1Decoder decoder, Av1FrameBuffer frameBuffer, ReadOnlySpan reference)
+ /// The zero-based sequence-frame index, or -1 for a standalone sample.
+ private static void AssertNativePlanesEqual(
+ Av1Decoder decoder,
+ Av1FrameBuffer frameBuffer,
+ ReadOnlySpan reference,
+ int frameIndex = -1)
{
(int chromaSubsamplingX, int chromaSubsamplingY) = frameBuffer.ColorFormat switch
{
@@ -3871,7 +3939,8 @@ public class Av1ReconstructionConformanceTests
largestExpected,
largestActual,
mismatchCount,
- mismatchDescription?.ToString() ?? string.Empty);
+ mismatchDescription?.ToString() ?? string.Empty,
+ frameIndex);
}
///
@@ -3894,6 +3963,7 @@ public class Av1ReconstructionConformanceTests
/// The reconstructed sample.
/// The total number of unequal native samples.
/// The first unequal samples in plane traversal order.
+ /// The zero-based sequence-frame index, or -1 for a standalone sample.
private static void AssertSampleEqual(
Av1Decoder decoder,
Av1Plane plane,
@@ -3902,7 +3972,8 @@ public class Av1ReconstructionConformanceTests
ushort expected,
ushort actual,
int mismatchCount,
- string mismatchDescription)
+ string mismatchDescription,
+ int frameIndex)
{
if (expected != actual)
{
@@ -3986,11 +4057,26 @@ public class Av1ReconstructionConformanceTests
Av1BlockModeInfo nextRowModeInfo = frameInfo.GetModeInfoAt(new Point(modeInfoColumn, nextModeInfoRow));
Av1BlockModeInfo aboveModeInfo = frameInfo.GetModeInfoAt(new Point(modeInfoColumn, Math.Max(blockRow - 1, 0)));
Av1BlockModeInfo leftModeInfo = frameInfo.GetModeInfoAt(new Point(Math.Max(blockColumn - 1, 0), modeInfoRow));
+ string frameDescription = frameIndex < 0 ? string.Empty : $"Frame {frameIndex}, ";
+ StringBuilder filmGrainCoefficientDescription = new();
+ ReadOnlySpan filmGrainCoefficients = frameHeader.FilmGrainParameters.ArCoeffsYPlus128;
+ int filmGrainCoefficientCount = 2 * (int)frameHeader.FilmGrainParameters.ArCoeffLag *
+ ((int)frameHeader.FilmGrainParameters.ArCoeffLag + 1);
+
+ for (int coefficientIndex = 0; coefficientIndex < filmGrainCoefficientCount; coefficientIndex++)
+ {
+ if (coefficientIndex != 0)
+ {
+ filmGrainCoefficientDescription.Append(',');
+ }
+
+ filmGrainCoefficientDescription.Append((int)filmGrainCoefficients[coefficientIndex] - 128);
+ }
// Exact conformance failures need the owning syntax state. A coordinate alone does not distinguish
// prediction, residual reconstruction, and in-loop filtering failures inside a large coded frame.
Assert.Fail(
- $"Plane {plane} differs at ({x}, {y}): expected {expected}, actual {actual}. "
+ $"{frameDescription}plane {plane} differs at ({x}, {y}): expected {expected}, actual {actual}. "
+ $"Total unequal samples={mismatchCount}:{mismatchDescription}. "
+ $"Block={modeInfo.BlockSize}, mode={modeInfo.YMode}, partition={modeInfo.PartitionType}, skip={modeInfo.Skip}, "
+ $"refs={modeInfo.ReferenceFrames[0]}/{modeInfo.ReferenceFrames[1]}, "
@@ -4006,7 +4092,16 @@ public class Av1ReconstructionConformanceTests
+ $"superblock-q={superblock.SuperblockQuantizerIndex}{coefficientDescription}, "
+ $"delta-lf={frameHeader.DeltaLoopFilterParameters.IsPresent}/{frameHeader.DeltaLoopFilterParameters.IsMulti}, "
+ $"CDEF={cdefStrengthIndex}/{cdefStrength}, restoration={frameHeader.LoopRestorationParameters.Items[0].Type}, "
- + $"film-grain={frameHeader.FilmGrainParameters.ApplyGrain}, "
+ + $"film-grain={frameHeader.FilmGrainParameters.ApplyGrain}/"
+ + $"overlap={frameHeader.FilmGrainParameters.OverlapFlag}/"
+ + $"update={frameHeader.FilmGrainParameters.UpdateGrain}/seed={frameHeader.FilmGrainParameters.GrainSeed}, "
+ + $"grain-points={frameHeader.FilmGrainParameters.NumYPoints}/"
+ + $"{frameHeader.FilmGrainParameters.NumCbPoints}/{frameHeader.FilmGrainParameters.NumCrPoints}, "
+ + $"grain-ar={frameHeader.FilmGrainParameters.ArCoeffLag}/"
+ + $"{frameHeader.FilmGrainParameters.ArCoeffShiftMinus6}, "
+ + $"grain-y-coefficients=[{filmGrainCoefficientDescription}], "
+ + $"grain-scale={frameHeader.FilmGrainParameters.GrainScalingMinus8}/"
+ + $"{frameHeader.FilmGrainParameters.GrainScaleShift}, "
+ $"tiles={frameHeader.TilesInfo.TileColumnCount}x{frameHeader.TilesInfo.TileRowCount}, "
+ $"first-tile-end=({frameHeader.TilesInfo.TileColumnStartModeInfo[1]}, {frameHeader.TilesInfo.TileRowStartModeInfo[1]}). "
+ $"Neighbors: above={aboveModeInfo.BlockSize}/{aboveModeInfo.YMode}/skip={aboveModeInfo.Skip}, "
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 23f7964dd..75f7cf1b6 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -1404,6 +1404,8 @@ public static class TestImages
public const string Av1OfficialTwoTemporalLayerSequenceNativeReference = "Heif/Av1/Conformance/libaom-av1-1-b8-22-svc-L1T2-libaom.y4m";
public const string Av1OfficialSpatialTemporalLayerSequence = "Heif/Av1/Conformance/libaom-av1-1-b8-22-svc-L2T2.ivf";
public const string Av1OfficialSpatialTemporalLayerSequenceNativeReference = "Heif/Av1/Conformance/libaom-av1-1-b8-22-svc-L2T2-libaom.y4m";
+ public const string Av1OfficialFilmGrainSequence = "Heif/Av1/Conformance/libaom-av1-1-b8-23-film-grain-50.ivf";
+ public const string Av1OfficialFilmGrainSequenceNativeReference = "Heif/Av1/Conformance/libaom-av1-1-b8-23-film-grain-50-libaom.y4m";
public const string Av1Cdef8BitPayload = "Heif/Av1/Conformance/libaom-cdef-kodim23-8b.bit";
public const string Av1Cdef8BitReference = "Heif/Av1/Conformance/libaom-cdef-kodim23-8b-libaom.yuv";
public const string Av1Cdef8BitAvif = "Heif/Av1/Conformance/libavif-cdef-kodim23-8b.avif";
diff --git a/tests/Images/Input/Heif/Av1/Conformance/libaom-av1-1-b8-23-film-grain-50-libaom.y4m b/tests/Images/Input/Heif/Av1/Conformance/libaom-av1-1-b8-23-film-grain-50-libaom.y4m
new file mode 100644
index 000000000..e312db6b5
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libaom-av1-1-b8-23-film-grain-50-libaom.y4m
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c71958de496a949d12e819190f240bd56010e5ad57d8a0d55903bf380a9e0c26
+size 1520738
diff --git a/tests/Images/Input/Heif/Av1/Conformance/libaom-av1-1-b8-23-film-grain-50.ivf b/tests/Images/Input/Heif/Av1/Conformance/libaom-av1-1-b8-23-film-grain-50.ivf
new file mode 100644
index 000000000..e298d56c5
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libaom-av1-1-b8-23-film-grain-50.ivf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ba3edd82a58414f009e1c821b947ec8de4e0420f33803ca7bfea39af6aeea155
+size 28634