diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md
index 2c4094c06..ccf5f2e6a 100644
--- a/HEIF_IMPLEMENTATION_PLAN.md
+++ b/HEIF_IMPLEMENTATION_PLAN.md
@@ -504,7 +504,7 @@ Implement and verify in dependency order:
- [x] Verify palette mode syntax and presented reconstruction with an independently encoded palette AVIF fixture.
- [ ] 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.
- - [ ] Verify lossless syntax, inverse quantization, prediction, and presented reconstruction with independently encoded 8/10/12-bit AVIF fixtures.
+ - [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.
- [x] Deblocking loop filter.
- [x] Implement allocation-free SIMD-first 4-, 6-, 8-, and 14-tap filtering for vertical and horizontal edges in 8/10/12-bit storage through closed edge operators, with exact scalar fallback and `FeatureTestRunner` parity against an independent definition.
- [x] Verify deblocking syntax, filter-level derivation, and boundary traversal with independently encoded 8/10/12-bit AV1 samples and exact scalar-libaom planes; verify presented reconstruction and public precision with genuine AVIF containers at every supported bit depth.
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
index d36c60984..fa4cb39d3 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
@@ -12,6 +12,18 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
///
internal static class Av1SymbolContextHelper
{
+ ///
+ /// Maps clipped top and left coefficient-level classes to the transform-block skip context.
+ ///
+ private static ReadOnlySpan TransformBlockSkipContexts =>
+ [
+ 1, 2, 2, 2, 3,
+ 2, 4, 4, 4, 5,
+ 2, 4, 4, 4, 5,
+ 2, 4, 4, 4, 5,
+ 3, 5, 5, 5, 6
+ ];
+
///
/// Maps each transform set and transform type to its coded symbol index.
///
@@ -93,6 +105,22 @@ internal static class Av1SymbolContextHelper
internal static Av1TransformSize GetTransformSizeContext(Av1TransformSize originalSize)
=> (Av1TransformSize)(((int)originalSize.GetSquareSize() + (int)originalSize.GetSquareUpSize() + 1) >> 1);
+ ///
+ /// Derives the luma transform-block skip context from the neighboring coefficient levels.
+ ///
+ /// The union of the packed coefficient contexts above the transform.
+ /// The union of the packed coefficient contexts to the left of the transform.
+ /// The transform-block skip context.
+ public static int GetTransformBlockSkipContext(int top, int left)
+ {
+ int topClass = Math.Min(top, 4);
+ int leftClass = Math.Min(left, 4);
+
+ // AV1 groups each edge into zero, low, or high coefficient-level classes. Retaining libaom's complete table
+ // lets the reader and writer share one compile-time mapping without an encoder-side jagged-array allocation.
+ return TransformBlockSkipContexts[(topClass * 5) + leftClass];
+ }
+
///
/// Reconstructs an end-of-block coefficient position from its token and extra offset.
///
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
index 36ad66125..d694e6448 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
@@ -82,12 +82,6 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
- ///
- /// Maps the minimum and union of luma neighbor levels to a transform-block skip context.
- ///
- private static readonly int[][] SkipContexts = [
- [1, 2, 2, 2, 3], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 6]];
-
///
/// Maps the weighted palette-neighbor score hash to its color-index entropy context.
///
@@ -1080,10 +1074,7 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable
while (++k < transformBlockUnitHighCount);
left &= mask;
- int max = Math.Min(top | left, 4);
- int min = Math.Min(Math.Min(top, left), 4);
-
- transformBlockContext.SkipContext = SkipContexts[min][max];
+ transformBlockContext.SkipContext = Av1SymbolContextHelper.GetTransformBlockSkipContext(top, left);
}
}
else
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
index ef130f777..143b7ce37 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
@@ -1406,10 +1406,6 @@ internal partial class Av1TileWriter
}
else
{
- // Luma skip contexts depend on the minimum and union of the clipped edge levels.
- byte[][] skip_contexts = [
- [1, 2, 2, 2, 3], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 6]
- ];
int top = 0;
int left = 0;
@@ -1438,10 +1434,7 @@ internal partial class Av1TileWriter
}
left &= Av1Constants.CoefficientContextMask;
- int max = Math.Min(top | left, 4);
- int min = Math.Min(Math.Min(top, left), 4);
-
- blockContext.SkipContext = skip_contexts[min][max];
+ blockContext.SkipContext = Av1SymbolContextHelper.GetTransformBlockSkipContext(top, left);
}
}
else
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseWalshHadamardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseWalshHadamardTransformer.cs
index 4b2306089..344f44f00 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseWalshHadamardTransformer.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseWalshHadamardTransformer.cs
@@ -139,10 +139,14 @@ internal static class Av1InverseWalshHadamardTransformer
row2 = Vector128.LoadUnsafe(ref coefficientBase, 8) >> UnitQuantizationShift;
row3 = Vector128.LoadUnsafe(ref coefficientBase, 12) >> UnitQuantizationShift;
+ // Entropy decoding normalizes AV1's column-major coefficient positions to the row-major transform
+ // workspace. Restore the normative dimension order before either reversible butterfly performs its
+ // signed half shift; swapping the dimensions after those shifts would not preserve lossless rounding.
+ Av1Transform2dOperations.Transpose(ref row0, ref row1, ref row2, ref row3);
Transform(ref row0, ref row1, ref row2, ref row3);
- // The first pass operates down four columns in parallel. Transposition turns those intermediate columns
- // into packed rows so the same reversible butterfly implements the second dimension without scratch.
+ // The first pass produces four packed intermediate columns. Transposition turns those columns into rows
+ // so the same reversible butterfly implements the second dimension without scratch.
Av1Transform2dOperations.Transpose(ref row0, ref row1, ref row2, ref row3);
Transform(ref row0, ref row1, ref row2, ref row3);
}
@@ -196,20 +200,22 @@ internal static class Av1InverseWalshHadamardTransformer
ref int coefficientBase = ref MemoryMarshal.GetReference(coefficients);
ref int intermediateBase = ref MemoryMarshal.GetReference(workspace);
- // The scalar fallback retains the column-first traversal. The caller-owned transform workspace keeps the
- // complete first dimension without introducing per-block stack or managed allocations.
- for (int column = 0; column < 4; column++)
+ // Entropy decoding stores the transposed scan in row-major order, so each contiguous local row is one
+ // normative transform column. Writing those results down the intermediate columns preserves libaom's
+ // dimension order without a separate transpose or per-block allocation.
+ for (int row = 0; row < 4; row++)
{
- int a = Unsafe.Add(ref coefficientBase, column) >> UnitQuantizationShift;
- int c = Unsafe.Add(ref coefficientBase, 4 + column) >> UnitQuantizationShift;
- int d = Unsafe.Add(ref coefficientBase, 8 + column) >> UnitQuantizationShift;
- int b = Unsafe.Add(ref coefficientBase, 12 + column) >> UnitQuantizationShift;
+ int coefficientOffset = row * 4;
+ int a = Unsafe.Add(ref coefficientBase, coefficientOffset) >> UnitQuantizationShift;
+ int c = Unsafe.Add(ref coefficientBase, coefficientOffset + 1) >> UnitQuantizationShift;
+ int d = Unsafe.Add(ref coefficientBase, coefficientOffset + 2) >> UnitQuantizationShift;
+ int b = Unsafe.Add(ref coefficientBase, coefficientOffset + 3) >> UnitQuantizationShift;
Transform(ref a, ref b, ref c, ref d);
- Unsafe.Add(ref intermediateBase, column) = a;
- Unsafe.Add(ref intermediateBase, 4 + column) = b;
- Unsafe.Add(ref intermediateBase, 8 + column) = c;
- Unsafe.Add(ref intermediateBase, 12 + column) = d;
+ Unsafe.Add(ref intermediateBase, row) = a;
+ Unsafe.Add(ref intermediateBase, 4 + row) = b;
+ Unsafe.Add(ref intermediateBase, 8 + row) = c;
+ Unsafe.Add(ref intermediateBase, 12 + row) = d;
}
for (int column = 0; column < 4; column++)
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs
index 01d535a2b..9f15d1c04 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs
@@ -411,18 +411,19 @@ public class Av1InverseTransformTests
}
int[] intermediateValues = new int[16];
- for (int column = 0; column < 4; column++)
+ for (int row = 0; row < 4; row++)
{
- int a = coefficients[column] >> 2;
- int c = coefficients[4 + column] >> 2;
- int d = coefficients[8 + column] >> 2;
- int b = coefficients[12 + column] >> 2;
+ int coefficientOffset = row * 4;
+ int a = coefficients[coefficientOffset] >> 2;
+ int c = coefficients[coefficientOffset + 1] >> 2;
+ int d = coefficients[coefficientOffset + 2] >> 2;
+ int b = coefficients[coefficientOffset + 3] >> 2;
ApplyWalshHadamardReference(ref a, ref b, ref c, ref d);
- intermediateValues[column] = a;
- intermediateValues[4 + column] = b;
- intermediateValues[8 + column] = c;
- intermediateValues[12 + column] = d;
+ intermediateValues[row] = a;
+ intermediateValues[4 + row] = b;
+ intermediateValues[8 + row] = c;
+ intermediateValues[12 + row] = d;
}
for (int column = 0; column < 4; column++)
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
index c313db469..0e6ad4f90 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
@@ -31,6 +31,11 @@ public class Av1ReconstructionConformanceTests
private const HwIntrinsics PaletteConfigurations =
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic;
+ ///
+ /// The hardware configurations covering the 128-bit and scalar lossless inverse-transform paths.
+ ///
+ private const HwIntrinsics LosslessConfigurations = HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic;
+
///
/// The hardware configurations covering the 256-bit, 128-bit, and scalar loop-restoration paths.
///
@@ -62,6 +67,16 @@ public class Av1ReconstructionConformanceTests
///
private const int RequiredPaletteCoverage = LumaPaletteCoverage | ChromaPaletteCoverage;
+ ///
+ /// The displayed width shared by the independent lossless fixtures.
+ ///
+ private const int LosslessFixtureWidth = 100;
+
+ ///
+ /// The displayed height shared by the independent lossless fixtures.
+ ///
+ private const int LosslessFixtureHeight = 60;
+
///
/// The hardware configurations covering the available vector widths and the scalar color-conversion fallback.
///
@@ -140,6 +155,22 @@ public class Av1ReconstructionConformanceTests
public void DecodeWithPaletteMatchesPinnedLibavifPresentation()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePalettePresentedFixture, PresentationConfigurations);
+ ///
+ /// Verifies lossless syntax, residual reconstruction, and exact native samples against scalar libaom for
+ /// independently encoded eight-, ten-, and twelve-bit AVIF images.
+ ///
+ [Fact]
+ public void DecodeLosslessMatchesPinnedLibaomReference()
+ => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateLosslessFixtures, LosslessConfigurations);
+
+ ///
+ /// Verifies exact presented pixels for independently encoded lossless eight-, ten-, and twelve-bit AVIF images
+ /// across the available vector widths and the scalar fallback.
+ ///
+ [Fact]
+ public void DecodeLosslessMatchesPinnedLibavifPresentation()
+ => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateLosslessPresentedFixtures, PresentationConfigurations);
+
///
/// Verifies active normative super-resolution, chroma-width rounding, replicated edges, and exact native samples
/// against scalar libaom for independently encoded eight-, ten-, and twelve-bit still-picture streams.
@@ -308,6 +339,56 @@ public class Av1ReconstructionConformanceTests
requireActiveLoopFilter: false,
requirePalette: true);
+ ///
+ /// Validates every lossless native fixture under the hardware configuration selected by
+ /// .
+ ///
+ private static void ValidateLosslessFixtures()
+ {
+ ValidateLosslessFixture(
+ TestImages.Heif.Av1Lossless8BitAvif,
+ TestImages.Heif.Av1Lossless8BitReference,
+ Av1BitDepth.EightBit);
+
+ ValidateLosslessFixture(
+ TestImages.Heif.Av1Lossless10BitAvif,
+ TestImages.Heif.Av1Lossless10BitReference,
+ Av1BitDepth.TenBit);
+
+ ValidateLosslessFixture(
+ TestImages.Heif.Av1Lossless12BitAvif,
+ TestImages.Heif.Av1Lossless12BitReference,
+ Av1BitDepth.TwelveBit);
+ }
+
+ ///
+ /// Validates every lossless presentation fixture under the hardware configuration selected by
+ /// .
+ ///
+ private static void ValidateLosslessPresentedFixtures()
+ {
+ ValidatePresentedFixture(
+ TestImages.Heif.Av1Lossless8BitAvif,
+ TestImages.Heif.Av1Lossless8BitPresentationReference,
+ LosslessFixtureWidth,
+ LosslessFixtureHeight,
+ HeifBitDepth.Bit8);
+
+ ValidatePresentedFixture(
+ TestImages.Heif.Av1Lossless10BitAvif,
+ TestImages.Heif.Av1Lossless10BitPresentationReference,
+ LosslessFixtureWidth,
+ LosslessFixtureHeight,
+ HeifBitDepth.Bit10);
+
+ ValidatePresentedFixture(
+ TestImages.Heif.Av1Lossless12BitAvif,
+ TestImages.Heif.Av1Lossless12BitPresentationReference,
+ LosslessFixtureWidth,
+ LosslessFixtureHeight,
+ HeifBitDepth.Bit12);
+ }
+
///
/// Validates every active super-resolution fixture under the hardware configuration selected by
/// .
@@ -701,6 +782,78 @@ public class Av1ReconstructionConformanceTests
return restorationCoverage;
}
+ ///
+ /// Validates lossless frame syntax and complete native reconstruction for one AVIF image.
+ ///
+ /// The independently encoded AVIF container.
+ /// The raw planar output produced by the pinned scalar libaom decoder.
+ /// The expected AV1 sample precision.
+ private static void ValidateLosslessFixture(string imagePath, string referencePath, Av1BitDepth bitDepth)
+ {
+ byte[] imageBytes = TestFile.Create(imagePath).Bytes;
+ byte[] referenceBytes = TestFile.Create(referencePath).Bytes;
+ Span payload = GetSoleAv1ItemPayload(imageBytes);
+ ReadOnlySpan nativeReference = referenceBytes;
+ using Av1Decoder decoder = new(Configuration.Default);
+ using Av1FrameBuffer frameBuffer = decoder.DecodeFrameBuffer(payload, null, null, out _);
+
+ Assert.Equal(LosslessFixtureWidth, frameBuffer.Width);
+ Assert.Equal(LosslessFixtureHeight, frameBuffer.Height);
+ Assert.Equal(bitDepth, frameBuffer.BitDepth);
+ Assert.Equal(Av1ColorFormat.Yuv444, frameBuffer.ColorFormat);
+ Assert.NotNull(decoder.SequenceHeader);
+ Assert.NotNull(decoder.FrameHeader);
+ Assert.NotNull(decoder.FrameInfo);
+ Assert.True(decoder.FrameHeader.CodedLossless);
+ Assert.True(decoder.FrameHeader.AllLossless);
+ Assert.Equal(0, decoder.FrameHeader.QuantizationParameters.BaseQIndex);
+ Assert.Equal(ObuMatrixCoefficients.Identity, decoder.SequenceHeader.ColorConfig.MatrixCoefficients);
+ Assert.False(decoder.FrameHeader.AllowIntraBlockCopy);
+ Assert.Equal(0, GetPaletteCoverage(decoder));
+
+ bool hasCodedResidual = false;
+ int superblockSizeLog2 = decoder.SequenceHeader.SuperblockSizeLog2;
+ int superblockColumnCount = Av1Math.AlignPowerOf2(decoder.SequenceHeader.MaxFrameWidth, superblockSizeLog2) >> superblockSizeLog2;
+ int superblockRowCount = Av1Math.AlignPowerOf2(decoder.SequenceHeader.MaxFrameHeight, superblockSizeLog2) >> superblockSizeLog2;
+ ReadOnlySpan planes = [Av1Plane.Y, Av1Plane.U, Av1Plane.V];
+ for (int superblockRow = 0; superblockRow < superblockRowCount && !hasCodedResidual; superblockRow++)
+ {
+ for (int superblockColumn = 0; superblockColumn < superblockColumnCount && !hasCodedResidual; superblockColumn++)
+ {
+ Point superblock = new(superblockColumn, superblockRow);
+ foreach (Av1Plane plane in planes)
+ {
+ Span coefficients = plane switch
+ {
+ Av1Plane.Y => decoder.FrameInfo.GetCoefficientsY(superblock),
+ Av1Plane.U => decoder.FrameInfo.GetCoefficientsU(superblock),
+ _ => decoder.FrameInfo.GetCoefficientsV(superblock)
+ };
+
+ // Each transform reserves an end index followed by its coefficients. Any nonzero stored value
+ // proves that exact output traversed coefficient decoding, inverse quantization, and lossless WHT.
+ foreach (int coefficient in coefficients)
+ {
+ if (coefficient != 0)
+ {
+ hasCodedResidual = true;
+ break;
+ }
+ }
+
+ if (hasCodedResidual)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ Assert.True(hasCodedResidual);
+
+ AssertNativePlanesEqual(frameBuffer, nativeReference);
+ }
+
///
/// Validates one independently encoded stream that activates constrained directional enhancement filtering.
///
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 15b3e017e..273eb20dd 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -1369,6 +1369,15 @@ public static class TestImages
public const string Av1Palette8BitReference = "Heif/Av1/Conformance/libaom-palette-draw-points-8b-444-libaom.yuv";
public const string Av1Palette8BitAvif = "Heif/Av1/Conformance/libavif-palette-draw-points-8b.avif";
public const string Av1Palette8BitPresentationReference = "Heif/Av1/Conformance/libavif-palette-draw-points-8b.png";
+ public const string Av1Lossless8BitAvif = "Heif/Av1/Conformance/libavif-lossless-circle-8b-444.avif";
+ public const string Av1Lossless8BitReference = "Heif/Av1/Conformance/libavif-lossless-circle-8b-444-libaom.yuv";
+ public const string Av1Lossless8BitPresentationReference = "Heif/Av1/Conformance/libavif-lossless-circle-8b-444.png";
+ public const string Av1Lossless10BitAvif = "Heif/Av1/Conformance/libavif-lossless-circle-10b-444.avif";
+ public const string Av1Lossless10BitReference = "Heif/Av1/Conformance/libavif-lossless-circle-10b-444-libaom.yuv";
+ public const string Av1Lossless10BitPresentationReference = "Heif/Av1/Conformance/libavif-lossless-circle-10b-444.png";
+ public const string Av1Lossless12BitAvif = "Heif/Av1/Conformance/libavif-lossless-circle-12b-444.avif";
+ public const string Av1Lossless12BitReference = "Heif/Av1/Conformance/libavif-lossless-circle-12b-444-libaom.yuv";
+ public const string Av1Lossless12BitPresentationReference = "Heif/Av1/Conformance/libavif-lossless-circle-12b-444.png";
public const string Av1SuperResolution8BitPayload = "Heif/Av1/Conformance/libaom-superres-kodim23-8b.bit";
public const string Av1SuperResolution8BitReference = "Heif/Av1/Conformance/libaom-superres-kodim23-8b-libaom.yuv";
public const string Av1SuperResolution8BitAvif = "Heif/Av1/Conformance/libavif-superres-kodim23-8b.avif";
diff --git a/tests/Images/Input/Heif/Av1/Conformance/README.md b/tests/Images/Input/Heif/Av1/Conformance/README.md
index f54b924ed..aca69c7a0 100644
--- a/tests/Images/Input/Heif/Av1/Conformance/README.md
+++ b/tests/Images/Input/Heif/Av1/Conformance/README.md
@@ -51,6 +51,12 @@ The palette fixture was encoded independently from ImageSharp using `tests/data/
`libaom-palette-draw-points-8b-444.bit` is the exact sole AV1 item extracted from `libavif-palette-draw-points-8b.avif`. The matching native YUV reference was decoded from that payload by the pinned scalar `aomdec --rawvideo` build. The presented PNG was decoded from the complete AVIF container by the pinned scalar `avifdec -j 1 -d 8` build. The tests require both palette planes to be selected, compare every native YUV sample exactly, and compare every presented RGBA byte exactly across the available vector widths and scalar fallback. No tolerance is used.
+## Lossless coverage
+
+The lossless fixtures were encoded independently from ImageSharp using `tests/data/circle-trns-after-plte.png` from the pinned libavif revision. Alpha was intentionally ignored so the native references isolate color-plane reconstruction. The 8-bit input uses CICP 1/13/0; the 10- and 12-bit YUV 4:4:4 inputs use CICP 12/16/0. The material `avifenc` options were `-j 1 -s 0 -l --ignore-alpha -y 444 -a enable-palette=0 -a enable-intrabc=0`, together with the matching depth and CICP values. Disabling palette and intra-block copy ensures the exact result traverses ordinary prediction, coefficient decoding, inverse quantization, and the reversible lossless transform.
+
+The `libavif-lossless-circle-*-444-libaom.yuv` files contain the headerless native planes decoded from the complete AVIF containers by the pinned generic `avifdec -j 1` build. Each file stores one 100x60 full-range YUV 4:4:4 frame at 8, 10, or 12 bits. The matching PNG files were decoded by the same build with `-d 8`. Tests require coded and complete losslessness, base quantizer zero, identity matrix coefficients, disabled palette and intra-block copy, and at least one coded residual. Every native Y, U, and V sample and every presented RGBA byte is compared exactly across normal hardware dispatch and the scalar fallback. No tolerance is used.
+
## Film-grain coverage
The film-grain pairs were generated independently from ImageSharp. Each `.bit` file is an AV1 still-picture OBU stream, and the matching `-libaom.yuv` file is the exact visible planar output from the pinned scalar libaom decoder.
diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-10b-444-libaom.yuv b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-10b-444-libaom.yuv
new file mode 100644
index 000000000..d1fe0f33f
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-10b-444-libaom.yuv
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:da9e962783072a3b2e8c0055125df1c909276ea5483345059e22706bdeea8121
+size 36000
diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-10b-444.avif b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-10b-444.avif
new file mode 100644
index 000000000..95b70d52f
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-10b-444.avif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dd55dd965f2023d15038a32f118c0c21d98053804f58d78d1e8f67365f1dcdf4
+size 2604
diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-10b-444.png b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-10b-444.png
new file mode 100644
index 000000000..4190c0bfe
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-10b-444.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f4ef1f4a117431ac32f887af4db38d4a90a910ddc5c120f8a716a4934a766c67
+size 1128
diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-12b-444-libaom.yuv b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-12b-444-libaom.yuv
new file mode 100644
index 000000000..8af8c4f3b
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-12b-444-libaom.yuv
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ad216d8499c02716a63d83ae8ce5f4aa0a211fb05b717dcc3b4467c649313a79
+size 36000
diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-12b-444.avif b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-12b-444.avif
new file mode 100644
index 000000000..6ac1a71e2
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-12b-444.avif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f4663d229c3f4d14abf60160acdd24846ceca2334615e5bc09f0f5f4128f0d0c
+size 3493
diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-12b-444.png b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-12b-444.png
new file mode 100644
index 000000000..208184b7e
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-12b-444.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:624d6bbc99ad1fb9a4bd658959f102c94aeeaab054d86901274832b0f8a12a95
+size 1152
diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-8b-444-libaom.yuv b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-8b-444-libaom.yuv
new file mode 100644
index 000000000..9fdf1a779
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-8b-444-libaom.yuv
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0daf2592a6e05243f46269f23d7cc4dcdaf24582d2b544e1dc3892d4c591f575
+size 18000
diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-8b-444.avif b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-8b-444.avif
new file mode 100644
index 000000000..ebcd12901
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-8b-444.avif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:63c0976280061543aeb6e0ca91bfdd982dbf1fff49ffe19a3fd967a83603e100
+size 1663
diff --git a/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-8b-444.png b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-8b-444.png
new file mode 100644
index 000000000..53ef7ef63
--- /dev/null
+++ b/tests/Images/Input/Heif/Av1/Conformance/libavif-lossless-circle-8b-444.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a9b2d70a65299c6da81c3b495c54e1eab99c1932e44add57cff2ae00c1f36072
+size 834