Browse Source

Verify AV1 profile reconstruction

pull/2633/head
James Jackson-South 1 week ago
parent
commit
d92e475c28
  1. 11
      HEIF_IMPLEMENTATION_PLAN.md
  2. 7
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs
  3. 121
      src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs
  4. 7
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1InverseQuantizer.cs
  5. 505
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs
  6. 36
      tests/ImageSharp.Tests/TestImages.cs
  7. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-400-libaom-y4m.yuv
  8. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-400.avif
  9. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-400.png
  10. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-420-libaom-y4m.yuv
  11. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-420.avif
  12. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-420.png
  13. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-422-libaom-y4m.yuv
  14. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-422.avif
  15. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-422.png
  16. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-444-libaom-y4m.yuv
  17. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-444.avif
  18. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-444.png
  19. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-400-libaom-y4m.yuv
  20. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-400.avif
  21. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-400.png
  22. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-420-libaom-y4m.yuv
  23. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-420.avif
  24. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-420.png
  25. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-422-libaom-y4m.yuv
  26. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-422.avif
  27. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-422.png
  28. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-444-libaom-y4m.yuv
  29. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-444.avif
  30. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-444.png
  31. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-400-libaom-y4m.yuv
  32. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-400.avif
  33. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-400.png
  34. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-420-libaom-y4m.yuv
  35. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-420.avif
  36. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-420.png
  37. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-422-libaom-y4m.yuv
  38. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-422.avif
  39. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-422.png
  40. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-444-libaom-y4m.yuv
  41. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-444.avif
  42. 3
      tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-444.png

11
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-27 against the worktree based on commit `cf0c81eb3`, including the completed AV1 transform, OBU-framing, and independently verified intra-block-copy checkpoints. 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-27 against the worktree based on commit `07cc0291e`, including the completed AV1 transform, OBU-framing, intra-block-copy, and 12-profile reconstruction checkpoints. 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.
Status meanings:
@ -40,13 +40,13 @@ Status meanings:
Current development stage: **Stage 3 — complete AV1 still-image decoding.** The transform checkpoint is closed: forward transforms use one libaom-shaped SIMD-first operator architecture across `Vector512`, `Vector256`, and `Vector128`, with scalar fallback; inverse production traversal uses the verified `Vector256` and `Vector128` tiers with scalar fallback; and implementation-mechanic type and file suffixes have been removed. Neither AV1 nor HEVC production encoding is implemented.
Immediate checkpoint: **remove every remaining valid AV1 still-image unsupported branch and prove the complete decode matrix.** Each syntax tool must be implemented through the established SIMD-first architecture with scalar fallback and verified with independent AVIF/libaom evidence across supported bit depths, chroma layouts, filters, grain, and color signaling.
Immediate checkpoint: **inventory and remove every remaining valid AV1 still-image unsupported branch, one independently verified syntax tool at a time.** The base AV1 profile matrix is now exact across 8/10/12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 reconstruction and presentation under every available dispatch tier. That matrix is the regression gate for the remaining compression-tool fixtures; it does not by itself prove every normative still-image tool.
| Order | Delivery stage | State | Delivered state | Gate that remains open |
| --- | --- | --- | --- | --- |
| 1 | Baseline, provenance, documentation, and public contract | In progress | Pinned codec references, a bounded image-only scope, encoder options, typed bit depth, decoder-option propagation, and extensive HEIF documentation exist. | Complete the all-file documentation audit, record a fresh Release baseline, finish distinct public HEIC/AVIF save boundaries, and close API review. |
| 2 | Bounded HEIF item and image-sequence container | In progress | Still-item parsing, grids, auxiliary alpha, metadata properties, bounded image-sequence tracks, Identify, and all-sync AV1 sequence presentation are connected. | Complete adversarial boundary coverage, remaining item/property behavior, reference-dependent sequence reconstruction, and the bounded sequence writer. |
| 3 | Still-image AV1 and HEVC decoding | **Current** | HEVC reconstruction reaches exact HM/libheif fixtures across the recorded 8/10/12-bit and chroma cases. AV1 includes bounded OBU framing, reconstruction, filters, grain, color, transforms, and independently verified intra-block-copy syntax and prediction through SIMD-first static-generic operators. | Remove every other valid AV1 still-image unsupported branch with independent vectors, then complete the remaining HEVC profile and Range Extensions matrix. |
| 3 | Still-image AV1 and HEVC decoding | **Current** | HEVC reconstruction reaches exact HM/libheif fixtures across the recorded 8/10/12-bit and chroma cases. AV1 includes bounded OBU framing, reconstruction, filters, grain, color, transforms, intra-block copy, and an exact independent 12-profile bit-depth/chroma matrix through every dispatch tier. | Remove every remaining valid AV1 still-image unsupported branch with independent compression-tool vectors, then complete the remaining HEVC profile and Range Extensions matrix. |
| 4 | Complete decoded presentation and animation | In progress | Shared SIMD-first AV1/HEVC color conversion, ICC application, grids, transforms, direct planar alpha composition, frame metadata, repetition, and independently decodable AV1 sequence samples exist. | Close the full color/ICC cross-product, HEVC sequence decoding, AV1/HEVC reference-dependent samples, frame-local metadata/alpha behavior, and independent animated decode vectors. |
| 5 | AV1/AVIF encoding | Not started | RGB-to-planar conversion, forward transforms, OBU writer foundations, options, and container-writing infrastructure exist. | `HeifEncoderCore` still rejects AV1. Implement a real independently decodable lossy/lossless AV1 payload and the complete AVIF item/metadata matrix. |
| 6 | HEVC/HEIC encoding | Not started | Shared input color conversion, options, and HEIF writer infrastructure exist. | `HeifEncoderCore` still rejects HEVC. Implement a real independently decodable lossy/lossless HEVC payload and the complete HEIC item/metadata matrix. |
@ -56,7 +56,8 @@ Immediate checkpoint: **remove every remaining valid AV1 still-image unsupported
## Immediate execution queue
- [x] Finish the libaom-shaped AV1 forward-transform architecture, measured production dispatch, inverse-tier correction, suffix cleanup, `FeatureTestRunner` matrix, and focused Release verification recorded below.
- [ ] **Current:** complete AV1 still-image decoding for every valid still syntax path and independently verify the full bit-depth, chroma, compression-tool, filter, grain, and color matrix.
- [x] Close the base AV1 profile matrix with exact native-plane and presented-image comparisons for 8/10/12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 fixtures under normal, AVX2, 128-bit, and scalar dispatch.
- [ ] **Current:** inventory and remove every remaining valid AV1 still-image unsupported branch, adding exact independent compression-tool fixtures to the profile-matrix regression gate.
- [ ] Complete the remaining HEVC still-image profile and Range Extensions matrix with exact independent native-plane and presentation evidence.
- [ ] Close shared decoded presentation, ICC, alpha, grid, transform, metadata, and animated AV1/HEVC decode gates.
- [ ] Implement and independently verify real AV1/AVIF still encoding.
@ -500,6 +501,8 @@ Implement and verify in dependency order:
- [ ] One coherent decoder lifecycle that retains parsed frame and tile state and disposes all buffers deterministically.
- [ ] Tile partitioning, mode information, segmentation, delta quantization, transform-size selection, coefficient token decode, inverse quantization, and inverse transforms.
- [x] Match libaom's depth-first traversal and frame-edge behavior for all ten AV1 partition types. Independent 8/10/12-bit streams collectively select every terminal partition shape and contain nested block geometry that requires recursive `Split` traversal; their complete native planes remain byte-exact under normal hardware dispatch and the scalar fallback.
- [x] Verify the complete 8/10/12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 base profile matrix against pinned libaom native planes and pinned libavif presentation output. All 12 fixtures match exactly with normal dispatch, AVX-512 disabled, AVX disabled, and all hardware intrinsics disabled through `FeatureTestRunner`.
- [x] Correct monochrome plane classification, maximum-superblock-row loop-filter traversal, and identity/one-dimensional inverse-quantization-matrix selection exposed by the profile matrix. The quantization rule matches libaom's `tx_type < IDTX` boundary and is verified by the exact native-plane oracle rather than a tolerance.
- [ ] Intra prediction, including every directional, smooth, Paeth, CFL, filter-intra, and palette case permitted by AV1.
- [x] Implement SIMD-first chroma-from-luma storage, 4:4:4/4:2:2/4:2:0 subsampling, rounded mean subtraction, and 8/10/12-bit prediction with exact scalar fallback and `FeatureTestRunner` parity.
- [x] Implement allocation-free SIMD-first palette reconstruction for palette sizes 2-8, transform widths 4-64, and 8/10/12-bit samples with exact scalar fallback and `FeatureTestRunner` parity.

7
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs

@ -90,6 +90,13 @@ internal class ObuColorConfig
/// <returns>The corresponding AV1 color format.</returns>
public Av1ColorFormat GetColorFormat()
{
if (this.IsMonochrome)
{
// AV1 sets both subsampling flags for monochrome sequences even though no chroma planes exist. The
// mono_chrome syntax therefore owns the plane layout and must take precedence over those derived flags.
return Av1ColorFormat.Yuv400;
}
Av1ColorFormat format = Av1ColorFormat.Yuv400;
if (this.SubSamplingX && this.SubSamplingY)
{

121
src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs

@ -61,7 +61,7 @@ internal class Av1LoopFilterDecoder
}
/// <summary>
/// Filters every enabled plane, processing all vertical boundaries before horizontal boundaries.
/// Filters every enabled plane in maximum-superblock row bands.
/// </summary>
public void DecodeFrame()
{
@ -72,45 +72,57 @@ internal class Av1LoopFilterDecoder
}
ObuColorConfig colorConfig = this.sequenceHeader.ColorConfig;
for (int planeIndex = 0; planeIndex < colorConfig.PlaneCount; planeIndex++)
int modeInfoRowsPerBand = 1 << (Av1Constants.MaxSuperBlockSizeLog2 - Av1Constants.ModeInfoSizeLog2);
// libaom's loop_filter_rows processes one MAX_MIB_SIZE band at a time so that a completed band can be
// presented before the remainder of the frame. The ordering is observable because the vertical and
// horizontal passes modify intersecting sample neighborhoods in place.
for (int rowStart = 0; rowStart < this.frameHeader.ModeInfoRowCount; rowStart += modeInfoRowsPerBand)
{
Av1Plane plane = (Av1Plane)planeIndex;
int planeFilterLevel = plane switch
{
Av1Plane.U => filterParameters.FilterLevelU,
Av1Plane.V => filterParameters.FilterLevelV,
_ => Math.Max(filterParameters.FilterLevel[0], filterParameters.FilterLevel[1])
};
int rowEnd = Math.Min(rowStart + modeInfoRowsPerBand, this.frameHeader.ModeInfoRowCount);
if (planeFilterLevel == 0)
for (int planeIndex = 0; planeIndex < colorConfig.PlaneCount; planeIndex++)
{
continue;
}
Av1Plane plane = (Av1Plane)planeIndex;
int planeFilterLevel = plane switch
{
Av1Plane.U => filterParameters.FilterLevelU,
Av1Plane.V => filterParameters.FilterLevelV,
_ => Math.Max(filterParameters.FilterLevel[0], filterParameters.FilterLevel[1])
};
int subX = plane != Av1Plane.Y && colorConfig.SubSamplingX ? 1 : 0;
int subY = plane != Av1Plane.Y && colorConfig.SubSamplingY ? 1 : 0;
Span<byte> lowBitDepthSamples = default;
Span<ushort> highBitDepthSamples = default;
int stride;
if (planeFilterLevel == 0)
{
continue;
}
if (this.frameBuffer.BytesPerSample == 2)
{
Span<short> signedSamples = this.frameBuffer.DeriveBlockPointer16(plane, Point.Empty, subX, subY, out stride);
highBitDepthSamples = MemoryMarshal.Cast<short, ushort>(signedSamples);
}
else
{
lowBitDepthSamples = this.frameBuffer.DeriveBlockPointer(plane, Point.Empty, subX, subY, out stride);
}
int subX = plane != Av1Plane.Y && colorConfig.SubSamplingX ? 1 : 0;
int subY = plane != Av1Plane.Y && colorConfig.SubSamplingY ? 1 : 0;
Span<byte> lowBitDepthSamples = default;
Span<ushort> highBitDepthSamples = default;
int stride;
this.FilterPlane(plane, subX, subY, stride, lowBitDepthSamples, highBitDepthSamples);
if (this.frameBuffer.BytesPerSample == 2)
{
Span<short> signedSamples = this.frameBuffer.DeriveBlockPointer16(plane, Point.Empty, subX, subY, out stride);
highBitDepthSamples = MemoryMarshal.Cast<short, ushort>(signedSamples);
}
else
{
lowBitDepthSamples = this.frameBuffer.DeriveBlockPointer(plane, Point.Empty, subX, subY, out stride);
}
this.FilterPlane(plane, rowStart, rowEnd, subX, subY, stride, lowBitDepthSamples, highBitDepthSamples);
}
}
}
/// <summary>
/// Filters one plane in the AV1 vertical-then-horizontal boundary order.
/// Filters one plane band in the AV1 vertical-then-horizontal boundary order.
/// </summary>
/// <param name="plane">The color plane to filter.</param>
/// <param name="rowStart">The inclusive band origin in luma 4x4 units.</param>
/// <param name="rowEnd">The exclusive band limit in luma 4x4 units.</param>
/// <param name="subX">The horizontal chroma subsampling shift.</param>
/// <param name="subY">The vertical chroma subsampling shift.</param>
/// <param name="stride">The plane stride in logical samples.</param>
@ -118,6 +130,8 @@ internal class Av1LoopFilterDecoder
/// <param name="highBitDepthSamples">The high-bit-depth plane storage, when active.</param>
private void FilterPlane(
Av1Plane plane,
int rowStart,
int rowEnd,
int subX,
int subY,
int stride,
@ -127,25 +141,41 @@ internal class Av1LoopFilterDecoder
int rowStep = 1 << subY;
int columnStep = 1 << subX;
// The AV1 result is independent of ordering within a pass, but vertical filtering must finish before any
// horizontal filtering begins because the two directions modify intersecting sample neighborhoods.
for (int pass = 0; pass < 2; pass++)
// The vertical pass advances across each row because successive vertical edges do not share modified samples.
// Chroma rows retain luma-grid coordinates and therefore advance by two mode-info units when subsampled.
for (int row = rowStart; row < rowEnd; row += rowStep)
{
for (int row = 0; row < this.frameHeader.ModeInfoRowCount; row += rowStep)
for (int column = 0; column < this.frameHeader.ModeInfoColumnCount; column += columnStep)
{
for (int column = 0; column < this.frameHeader.ModeInfoColumnCount; column += columnStep)
{
this.FilterEdge(
plane,
pass,
row,
column,
subX,
subY,
stride,
lowBitDepthSamples,
highBitDepthSamples);
}
this.FilterEdge(
plane,
0,
row,
column,
subX,
subY,
stride,
lowBitDepthSamples,
highBitDepthSamples);
}
}
// Horizontal edges are visited down each column. This ordering is observable because adjacent horizontal
// filters can modify samples that a later edge reads, so it must match libaom's av1_filter_block_plane_horz.
for (int column = 0; column < this.frameHeader.ModeInfoColumnCount; column += columnStep)
{
for (int row = rowStart; row < rowEnd; row += rowStep)
{
this.FilterEdge(
plane,
1,
row,
column,
subX,
subY,
stride,
lowBitDepthSamples,
highBitDepthSamples);
}
}
}
@ -247,6 +277,7 @@ internal class Av1LoopFilterDecoder
int boundaryLimit = (2 * (filterLevel + 2)) + limit;
int highEdgeVarianceThreshold = filterLevel >> 4;
int q0Offset = stride + (planeY * stride) + planeX;
if (this.frameBuffer.BytesPerSample == 2)
{
if (verticalBoundary)

7
src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1InverseQuantizer.cs

@ -95,13 +95,14 @@ internal class Av1InverseQuantizer
short dequantDc = this.deQuantsDeltaQ.GetDc(mode.SegmentId, plane);
short dequantAc = this.deQuantsDeltaQ.GetAc(mode.SegmentId, plane);
// The final matrix level is flat. Lossless blocks, frames without matrices, and one-dimensional transforms
// must use it so coefficient frequency does not change the signaled dequantization value.
// The final matrix level is flat. Lossless blocks and frames without matrices select it globally. AV1 also
// requires identity and one-dimensional transform types, which occupy the enum range from Identity onward,
// to bypass frequency weighting even when the frame signals quantization matrices.
int qmLevel = lossless || !usingQuantizationMatrix
? Av1ScanOrderConstants.QuantizationMatrixLevelCount - 1
: this.frameHeader.SegmentationParameters.QMLevel[(int)plane][mode.SegmentId];
ReadOnlySpan<int> iqMatrix = (transformType.ToClass() == Av1TransformClass.Class2D)
ReadOnlySpan<int> iqMatrix = transformType < Av1TransformType.Identity
? Av1InverseQuantizationLookup.GetQuantizationMatrix(qmLevel, plane, transformSize)
: Av1InverseQuantizationLookup.GetQuantizationMatrix(Av1Constants.QuantificationMatrixLevelCount - 1, Av1Plane.Y, transformSize);

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

@ -2,12 +2,15 @@
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Heif;
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -20,6 +23,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
[Trait("Format", "Avif")]
public class Av1ReconstructionConformanceTests
{
/// <summary>
/// The width and height of one CDEF unit in 4x4 luma mode-information units.
/// </summary>
private const int CdefUnitModeInfoSize = 16;
/// <summary>
/// The hardware configurations covering normal SIMD dispatch and the scalar fallback.
/// </summary>
@ -37,6 +45,12 @@ public class Av1ReconstructionConformanceTests
private const HwIntrinsics IntraBlockCopyConfigurations =
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic;
/// <summary>
/// The hardware configurations covering the narrower vector widths and scalar fallback for the profile matrix.
/// </summary>
private const HwIntrinsics ProfileFallbackConfigurations =
HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic;
/// <summary>
/// The hardware configurations covering the 128-bit and scalar lossless inverse-transform paths.
/// </summary>
@ -88,6 +102,16 @@ public class Av1ReconstructionConformanceTests
/// </summary>
private const int LosslessFixtureHeight = 60;
/// <summary>
/// The displayed width shared by the independent AV1 profile fixtures.
/// </summary>
private const int ProfileFixtureWidth = 512;
/// <summary>
/// The displayed height shared by the independent AV1 profile fixtures.
/// </summary>
private const int ProfileFixtureHeight = 256;
/// <summary>
/// The hardware configurations covering the available vector widths and the scalar color-conversion fallback.
/// </summary>
@ -150,6 +174,38 @@ public class Av1ReconstructionConformanceTests
public void DecodeWithActiveCdefMatchesPinnedLibavifPresentation()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePresentedFixtures, PresentationConfigurations);
/// <summary>
/// Verifies exact native reconstruction for every valid AV1 profile, bit-depth, and chroma-format combination
/// supported by AVIF across every available vector width and the scalar fallback.
/// </summary>
[Fact]
public void DecodeProfileMatrixMatchesPinnedLibaomReference()
=> ValidateProfileNativeFixtures();
/// <summary>
/// Verifies exact native reconstruction for every valid AV1 profile, bit-depth, and chroma-format combination
/// under each narrower vector width and the scalar fallback.
/// </summary>
[Fact]
public void DecodeProfileMatrixFallbacksMatchPinnedLibaomReference()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateProfileNativeFixtures, ProfileFallbackConfigurations);
/// <summary>
/// Verifies exact presented pixels, public bit-depth metadata, and CICP signaling for every valid AV1 profile,
/// bit-depth, and chroma-format combination supported by AVIF.
/// </summary>
[Fact]
public void DecodeProfileMatrixMatchesPinnedLibavifPresentation()
=> ValidateProfilePresentedFixtures();
/// <summary>
/// Verifies exact presented pixels, public bit-depth metadata, and CICP signaling under each narrower vector width
/// and the scalar fallback.
/// </summary>
[Fact]
public void DecodeProfileMatrixFallbacksMatchPinnedLibavifPresentation()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateProfilePresentedFixtures, ProfileFallbackConfigurations);
/// <summary>
/// Verifies decoded luma and chroma palette syntax and exact native samples against scalar libaom for an
/// independently encoded AV1 still-picture stream.
@ -292,6 +348,275 @@ public class Av1ReconstructionConformanceTests
Assert.Equal(RequiredPartitionCoverage, coverage & RequiredPartitionCoverage);
}
/// <summary>
/// Validates every native profile fixture under the hardware configuration selected by
/// <see cref="FeatureTestRunner"/>.
/// </summary>
private static void ValidateProfileNativeFixtures()
{
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile8BitMonochromeAvif,
TestImages.Heif.Av1Profile8BitMonochromeReference,
Av1BitDepth.EightBit,
Av1ColorFormat.Yuv400,
ObuSequenceProfile.Main);
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile8Bit420Avif,
TestImages.Heif.Av1Profile8Bit420Reference,
Av1BitDepth.EightBit,
Av1ColorFormat.Yuv420,
ObuSequenceProfile.Main);
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile8Bit422Avif,
TestImages.Heif.Av1Profile8Bit422Reference,
Av1BitDepth.EightBit,
Av1ColorFormat.Yuv422,
ObuSequenceProfile.Professional);
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile8Bit444Avif,
TestImages.Heif.Av1Profile8Bit444Reference,
Av1BitDepth.EightBit,
Av1ColorFormat.Yuv444,
ObuSequenceProfile.High);
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile10BitMonochromeAvif,
TestImages.Heif.Av1Profile10BitMonochromeReference,
Av1BitDepth.TenBit,
Av1ColorFormat.Yuv400,
ObuSequenceProfile.Main);
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile10Bit420Avif,
TestImages.Heif.Av1Profile10Bit420Reference,
Av1BitDepth.TenBit,
Av1ColorFormat.Yuv420,
ObuSequenceProfile.Main);
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile10Bit422Avif,
TestImages.Heif.Av1Profile10Bit422Reference,
Av1BitDepth.TenBit,
Av1ColorFormat.Yuv422,
ObuSequenceProfile.Professional);
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile10Bit444Avif,
TestImages.Heif.Av1Profile10Bit444Reference,
Av1BitDepth.TenBit,
Av1ColorFormat.Yuv444,
ObuSequenceProfile.High);
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile12BitMonochromeAvif,
TestImages.Heif.Av1Profile12BitMonochromeReference,
Av1BitDepth.TwelveBit,
Av1ColorFormat.Yuv400,
ObuSequenceProfile.Professional);
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile12Bit420Avif,
TestImages.Heif.Av1Profile12Bit420Reference,
Av1BitDepth.TwelveBit,
Av1ColorFormat.Yuv420,
ObuSequenceProfile.Professional);
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile12Bit422Avif,
TestImages.Heif.Av1Profile12Bit422Reference,
Av1BitDepth.TwelveBit,
Av1ColorFormat.Yuv422,
ObuSequenceProfile.Professional);
ValidateProfileNativeFixture(
TestImages.Heif.Av1Profile12Bit444Avif,
TestImages.Heif.Av1Profile12Bit444Reference,
Av1BitDepth.TwelveBit,
Av1ColorFormat.Yuv444,
ObuSequenceProfile.Professional);
}
/// <summary>
/// Validates every presented profile fixture under the hardware configuration selected by
/// <see cref="FeatureTestRunner"/>.
/// </summary>
private static void ValidateProfilePresentedFixtures()
{
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile8BitMonochromeAvif,
TestImages.Heif.Av1Profile8BitMonochromePresentationReference,
HeifBitDepth.Bit8);
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile8Bit420Avif,
TestImages.Heif.Av1Profile8Bit420PresentationReference,
HeifBitDepth.Bit8);
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile8Bit422Avif,
TestImages.Heif.Av1Profile8Bit422PresentationReference,
HeifBitDepth.Bit8);
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile8Bit444Avif,
TestImages.Heif.Av1Profile8Bit444PresentationReference,
HeifBitDepth.Bit8);
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile10BitMonochromeAvif,
TestImages.Heif.Av1Profile10BitMonochromePresentationReference,
HeifBitDepth.Bit10);
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile10Bit420Avif,
TestImages.Heif.Av1Profile10Bit420PresentationReference,
HeifBitDepth.Bit10);
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile10Bit422Avif,
TestImages.Heif.Av1Profile10Bit422PresentationReference,
HeifBitDepth.Bit10);
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile10Bit444Avif,
TestImages.Heif.Av1Profile10Bit444PresentationReference,
HeifBitDepth.Bit10);
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile12BitMonochromeAvif,
TestImages.Heif.Av1Profile12BitMonochromePresentationReference,
HeifBitDepth.Bit12);
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile12Bit420Avif,
TestImages.Heif.Av1Profile12Bit420PresentationReference,
HeifBitDepth.Bit12);
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile12Bit422Avif,
TestImages.Heif.Av1Profile12Bit422PresentationReference,
HeifBitDepth.Bit12);
ValidateProfilePresentedFixture(
TestImages.Heif.Av1Profile12Bit444Avif,
TestImages.Heif.Av1Profile12Bit444PresentationReference,
HeifBitDepth.Bit12);
}
/// <summary>
/// Validates one independently encoded AVIF against its native Y4M reference and signaled sequence profile.
/// </summary>
/// <param name="imagePath">The complete AVIF container.</param>
/// <param name="referencePath">The native Y4M output produced by the pinned scalar libaom-backed decoder.</param>
/// <param name="bitDepth">The expected AV1 sample precision.</param>
/// <param name="colorFormat">The expected native chroma-sampling layout.</param>
/// <param name="sequenceProfile">The AV1 profile required by the bit-depth and chroma-format combination.</param>
private static void ValidateProfileNativeFixture(
string imagePath,
string referencePath,
Av1BitDepth bitDepth,
Av1ColorFormat colorFormat,
ObuSequenceProfile sequenceProfile)
{
byte[] imageBytes = TestFile.Create(imagePath).Bytes;
byte[] referenceBytes = TestFile.Create(referencePath).Bytes;
(string chromaTag, string extendedChromaTag) = GetY4mColorSpace(bitDepth, colorFormat);
string expectedHeader =
$"YUV4MPEG2 W{ProfileFixtureWidth} H{ProfileFixtureHeight} F25:1 Ip A0:0 C{chromaTag} XYSCSS={extendedChromaTag} XCOLORRANGE=FULL\n";
int headerTerminator = referenceBytes.AsSpan().IndexOf((byte)'\n');
Assert.NotEqual(-1, headerTerminator);
int fileHeaderLength = headerTerminator + 1;
Assert.Equal(expectedHeader, Encoding.ASCII.GetString(referenceBytes, 0, fileHeaderLength));
ReadOnlySpan<byte> nativeReference = referenceBytes.AsSpan(fileHeaderLength);
ReadOnlySpan<byte> frameHeader = "FRAME\n"u8;
Assert.True(nativeReference.StartsWith(frameHeader));
nativeReference = nativeReference[frameHeader.Length..];
Span<byte> payload = GetSoleAv1ItemPayload(imageBytes);
using Av1Decoder decoder = new(Configuration.Default);
using Av1FrameBuffer<byte> frameBuffer = decoder.DecodeFrameBuffer(payload, null, null, out _);
Assert.Equal(ProfileFixtureWidth, frameBuffer.Width);
Assert.Equal(ProfileFixtureHeight, frameBuffer.Height);
Assert.Equal(bitDepth, frameBuffer.BitDepth);
Assert.Equal(colorFormat, frameBuffer.ColorFormat);
ObuSequenceHeader sequenceHeader = Assert.IsType<ObuSequenceHeader>(decoder.SequenceHeader);
ObuColorConfig colorConfig = sequenceHeader.ColorConfig;
Assert.Equal(sequenceProfile, sequenceHeader.SequenceProfile);
Assert.Equal(bitDepth, colorConfig.BitDepth);
Assert.Equal(colorFormat, colorConfig.GetColorFormat());
Assert.Equal(colorFormat == Av1ColorFormat.Yuv400, colorConfig.IsMonochrome);
Assert.True(colorConfig.IsColorDescriptionPresent);
Assert.Equal(ObuColorPrimaries.Bt709, colorConfig.ColorPrimaries);
Assert.Equal(ObuTransferCharacteristics.Srgb, colorConfig.TransferCharacteristics);
Assert.Equal(ObuMatrixCoefficients.Bt601, colorConfig.MatrixCoefficients);
Assert.True(colorConfig.ColorRange);
AssertNativePlanesEqual(decoder, frameBuffer, nativeReference);
}
/// <summary>
/// Validates the exact public presentation and metadata of one independently encoded AVIF profile fixture.
/// </summary>
/// <param name="imagePath">The complete AVIF container.</param>
/// <param name="referencePath">The eight-bit RGBA output produced by the pinned scalar libavif decoder.</param>
/// <param name="metadataBitDepth">The expected public HEIF sample precision.</param>
private static void ValidateProfilePresentedFixture(string imagePath, string referencePath, HeifBitDepth metadataBitDepth)
{
DecoderOptions options = new() { MaxFrames = 1 };
byte[] imageBytes = TestFile.Create(imagePath).Bytes;
byte[] referenceBytes = TestFile.Create(referencePath).Bytes;
using Image<Rgba32> image = Image.Load<Rgba32>(options, imageBytes);
using Image<Rgba32> reference = Image.Load<Rgba32>(referenceBytes);
Assert.Equal(ProfileFixtureWidth, image.Width);
Assert.Equal(ProfileFixtureHeight, image.Height);
Assert.Single(image.Frames);
HeifMetadata metadata = image.Metadata.GetHeifMetadata();
Assert.Equal(HeifCompressionMethod.Av1, metadata.CompressionMethod);
Assert.Equal(metadataBitDepth, metadata.BitDepth);
CicpProfile colorProfile = Assert.IsType<CicpProfile>(image.Metadata.CicpProfile);
Assert.Equal(CicpColorPrimaries.ItuRBt709_6, colorProfile.ColorPrimaries);
Assert.Equal(CicpTransferCharacteristics.Iec61966_2_1, colorProfile.TransferCharacteristics);
Assert.Equal(CicpMatrixCoefficients.ItuRBt601_7_525, colorProfile.MatrixCoefficients);
Assert.True(colorProfile.FullRange);
ImageComparer.Exact.VerifySimilarity(reference, image);
}
/// <summary>
/// Gets the Y4M chroma tags that encode one AV1 bit-depth and sampling-layout combination.
/// </summary>
/// <param name="bitDepth">The encoded AV1 sample precision.</param>
/// <param name="colorFormat">The encoded AV1 chroma-sampling layout.</param>
/// <returns>The Y4M <c>C</c> tag and extended <c>XYSCSS</c> tag.</returns>
private static (string ChromaTag, string ExtendedChromaTag) GetY4mColorSpace(Av1BitDepth bitDepth, Av1ColorFormat colorFormat)
{
// Y4M uses a legacy 420jpeg name at eight bits, lowercase p in high-depth C tags, and uppercase P in the
// corresponding XYSCSS tags. Keeping the exact spellings detects a reference generated with different layout.
return (bitDepth, colorFormat) switch
{
(Av1BitDepth.EightBit, Av1ColorFormat.Yuv400) => ("mono", "400"),
(Av1BitDepth.EightBit, Av1ColorFormat.Yuv420) => ("420jpeg", "420JPEG"),
(Av1BitDepth.EightBit, Av1ColorFormat.Yuv422) => ("422", "422"),
(Av1BitDepth.EightBit, Av1ColorFormat.Yuv444) => ("444", "444"),
(Av1BitDepth.TenBit, Av1ColorFormat.Yuv400) => ("mono10", "400"),
(Av1BitDepth.TenBit, Av1ColorFormat.Yuv420) => ("420p10", "420P10"),
(Av1BitDepth.TenBit, Av1ColorFormat.Yuv422) => ("422p10", "422P10"),
(Av1BitDepth.TenBit, Av1ColorFormat.Yuv444) => ("444p10", "444P10"),
(Av1BitDepth.TwelveBit, Av1ColorFormat.Yuv400) => ("mono12", "400"),
(Av1BitDepth.TwelveBit, Av1ColorFormat.Yuv420) => ("420p12", "420P12"),
(Av1BitDepth.TwelveBit, Av1ColorFormat.Yuv422) => ("422p12", "422P12"),
_ => ("444p12", "444P12")
};
}
/// <summary>
/// Validates every active-CDEF fixture under the hardware configuration selected by <see cref="FeatureTestRunner"/>.
/// </summary>
@ -439,7 +764,7 @@ public class Av1ReconstructionConformanceTests
Assert.NotNull(decoder.FrameHeader);
Assert.True(decoder.FrameHeader.AllowIntraBlockCopy);
Assert.NotEqual(0, GetIntraBlockCopyBlockCount(decoder));
AssertNativePlanesEqual(frameBuffer, nativeReference);
AssertNativePlanesEqual(decoder, frameBuffer, nativeReference);
}
/// <summary>
@ -912,7 +1237,7 @@ public class Av1ReconstructionConformanceTests
Assert.Equal(RequiredPaletteCoverage, GetPaletteCoverage(decoder));
}
AssertNativePlanesEqual(frameBuffer, reference);
AssertNativePlanesEqual(decoder, frameBuffer, reference);
return restorationCoverage;
}
@ -985,7 +1310,7 @@ public class Av1ReconstructionConformanceTests
Assert.True(hasCodedResidual);
AssertNativePlanesEqual(frameBuffer, nativeReference);
AssertNativePlanesEqual(decoder, frameBuffer, nativeReference);
}
/// <summary>
@ -1398,9 +1723,10 @@ public class Av1ReconstructionConformanceTests
/// <summary>
/// Compares every visible native component sample with the independent planar reference.
/// </summary>
/// <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="reference">The planar Y, U, and V samples produced by the pinned libaom decoder.</param>
private static void AssertNativePlanesEqual(Av1FrameBuffer<byte> frameBuffer, ReadOnlySpan<byte> reference)
private static void AssertNativePlanesEqual(Av1Decoder decoder, Av1FrameBuffer<byte> frameBuffer, ReadOnlySpan<byte> reference)
{
(int chromaSubsamplingX, int chromaSubsamplingY) = frameBuffer.ColorFormat switch
{
@ -1413,6 +1739,13 @@ public class Av1ReconstructionConformanceTests
ReadOnlySpan<Av1Plane> planes = frameBuffer.ColorFormat == Av1ColorFormat.Yuv400
? [Av1Plane.Y]
: [Av1Plane.Y, Av1Plane.U, Av1Plane.V];
int mismatchCount = 0;
Av1Plane largestMismatchPlane = default;
int largestMismatchX = 0;
int largestMismatchY = 0;
ushort largestExpected = 0;
ushort largestActual = 0;
StringBuilder mismatchDescription = null;
foreach (Av1Plane plane in planes)
{
@ -1430,7 +1763,25 @@ public class Av1ReconstructionConformanceTests
ReadOnlySpan<byte> expectedRow = reference.Slice(referenceOffset, planeWidth);
for (int x = 0; x < planeWidth; x++)
{
AssertSampleEqual(plane, x, y, expectedRow[x], actualRow[x]);
if (expectedRow[x] != actualRow[x])
{
if (mismatchCount < 16)
{
mismatchDescription ??= new StringBuilder();
mismatchDescription.Append($" {plane}({x},{y})={expectedRow[x]}/{actualRow[x]}");
}
if (mismatchCount == 0 || Math.Abs(expectedRow[x] - actualRow[x]) > Math.Abs(largestExpected - largestActual))
{
largestMismatchPlane = plane;
largestMismatchX = x;
largestMismatchY = y;
largestExpected = expectedRow[x];
largestActual = actualRow[x];
}
mismatchCount++;
}
}
referenceOffset += planeWidth;
@ -1445,7 +1796,26 @@ public class Av1ReconstructionConformanceTests
for (int x = 0; x < planeWidth; x++)
{
ushort expected = BinaryPrimitives.ReadUInt16LittleEndian(reference.Slice(referenceOffset, sizeof(ushort)));
AssertSampleEqual(plane, x, y, expected, actualRow[x]);
if (expected != actualRow[x])
{
if (mismatchCount < 16)
{
mismatchDescription ??= new StringBuilder();
mismatchDescription.Append($" {plane}({x},{y})={expected}/{actualRow[x]}");
}
if (mismatchCount == 0 || Math.Abs(expected - actualRow[x]) > Math.Abs(largestExpected - largestActual))
{
largestMismatchPlane = plane;
largestMismatchX = x;
largestMismatchY = y;
largestExpected = expected;
largestActual = actualRow[x];
}
mismatchCount++;
}
referenceOffset += sizeof(ushort);
}
}
@ -1453,6 +1823,15 @@ public class Av1ReconstructionConformanceTests
}
Assert.Equal(reference.Length, referenceOffset);
AssertSampleEqual(
decoder,
largestMismatchPlane,
largestMismatchX,
largestMismatchY,
largestExpected,
largestActual,
mismatchCount,
mismatchDescription?.ToString() ?? string.Empty);
}
/// <summary>
@ -1467,16 +1846,126 @@ public class Av1ReconstructionConformanceTests
/// <summary>
/// Reports the exact component coordinate when independently decoded samples differ.
/// </summary>
/// <param name="decoder">The decoder state used to identify the coded block containing the sample.</param>
/// <param name="plane">The compared component plane.</param>
/// <param name="x">The sample X coordinate.</param>
/// <param name="y">The sample Y coordinate.</param>
/// <param name="expected">The reference sample.</param>
/// <param name="actual">The reconstructed sample.</param>
private static void AssertSampleEqual(Av1Plane plane, int x, int y, ushort expected, ushort actual)
/// <param name="mismatchCount">The total number of unequal native samples.</param>
/// <param name="mismatchDescription">The first unequal samples in plane traversal order.</param>
private static void AssertSampleEqual(
Av1Decoder decoder,
Av1Plane plane,
int x,
int y,
ushort expected,
ushort actual,
int mismatchCount,
string mismatchDescription)
{
if (expected != actual)
{
Assert.Fail($"Plane {plane} differs at ({x}, {y}): expected {expected}, actual {actual}.");
Av1FrameInfo frameInfo = Assert.IsType<Av1FrameInfo>(decoder.FrameInfo);
int modeInfoColumn = x >> Av1Constants.ModeInfoSizeLog2;
int modeInfoRow = y >> Av1Constants.ModeInfoSizeLog2;
Av1BlockModeInfo modeInfo = frameInfo.GetModeInfoAt(new Point(modeInfoColumn, modeInfoRow));
int blockColumn = modeInfoColumn;
while (blockColumn > 0 && ReferenceEquals(frameInfo.GetModeInfoAt(new Point(blockColumn - 1, modeInfoRow)), modeInfo))
{
blockColumn--;
}
int blockRow = modeInfoRow;
while (blockRow > 0 && ReferenceEquals(frameInfo.GetModeInfoAt(new Point(modeInfoColumn, blockRow - 1)), modeInfo))
{
blockRow--;
}
int superblockSize = frameInfo.SuperblockModeInfoSize;
Av1SuperblockInfo superblock = frameInfo.GetSuperblock(new Point(blockColumn / superblockSize, blockRow / superblockSize));
Span<Av1TransformInfo> transforms = superblock.GetTransformInfoY().Slice(
modeInfo.GetFirstTransformLocation(Av1Plane.Y),
modeInfo.GetTransformUnitCount(Av1Plane.Y));
Av1TransformInfo containingTransform = transforms[0];
int containingTransformIndex = 0;
int transformColumn = modeInfoColumn - blockColumn;
int transformRow = modeInfoRow - blockRow;
for (int transformIndex = 0; transformIndex < transforms.Length; transformIndex++)
{
Av1TransformInfo transform = transforms[transformIndex];
if (transformColumn >= transform.OffsetX && transformColumn < transform.OffsetX + transform.Size.Get4x4WideCount()
&& transformRow >= transform.OffsetY && transformRow < transform.OffsetY + transform.Size.Get4x4HighCount())
{
containingTransform = transform;
containingTransformIndex = transformIndex;
break;
}
}
int superblockTransformIndex = modeInfo.GetFirstTransformLocation(Av1Plane.Y) + containingTransformIndex;
Span<Av1TransformInfo> superblockTransforms = superblock.GetTransformInfoY();
Span<int> superblockCoefficients = superblock.CoefficientsY;
int coefficientOffset = 0;
for (int transformIndex = 0; transformIndex < superblockTransformIndex; transformIndex++)
{
if (superblockTransforms[transformIndex].CodeBlockFlag)
{
coefficientOffset += superblockCoefficients[coefficientOffset] + 1;
}
}
StringBuilder coefficientDescription = new();
if (containingTransform.CodeBlockFlag)
{
int coefficientCount = superblockCoefficients[coefficientOffset];
coefficientDescription.Append($", quantized-coefficients={coefficientCount}:[");
for (int coefficientIndex = 0; coefficientIndex < coefficientCount; coefficientIndex++)
{
if (coefficientIndex != 0)
{
coefficientDescription.Append(',');
}
coefficientDescription.Append(superblockCoefficients[coefficientOffset + coefficientIndex + 1]);
}
coefficientDescription.Append(']');
}
ObuFrameHeader frameHeader = Assert.IsType<ObuFrameHeader>(decoder.FrameHeader);
int cdefUnitColumn = (modeInfoColumn % superblockSize) / CdefUnitModeInfoSize;
int cdefUnitRow = (modeInfoRow % superblockSize) / CdefUnitModeInfoSize;
int cdefStrengthIndex = frameInfo.GetCdefStrength(superblock.Position)[cdefUnitColumn + (cdefUnitRow << 1)];
int cdefStrength = cdefStrengthIndex < 0 ? -1 : frameHeader.CdefParameters.YStrength[cdefStrengthIndex];
int nextModeInfoRow = Math.Min(modeInfoRow + 1, frameHeader.ModeInfoRowCount - 1);
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));
// 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}. "
+ $"Total unequal samples={mismatchCount}:{mismatchDescription}. "
+ $"Block={modeInfo.BlockSize}, mode={modeInfo.YMode}, partition={modeInfo.PartitionType}, skip={modeInfo.Skip}, "
+ $"filter-intra={modeInfo.UseFilterIntra}/{modeInfo.FilterIntraMode}, angle-delta={modeInfo.GetAngleDelta(plane)}, "
+ $"palette-size={modeInfo.GetPaletteSize(plane)}, transforms={modeInfo.GetTransformUnitCount(plane)}, "
+ $"transform={containingTransform.Size}/{containingTransform.Type}/coded={containingTransform.CodeBlockFlag} "
+ $"at ({containingTransform.OffsetX}, {containingTransform.OffsetY}), block-origin=({blockColumn}, {blockRow}). "
+ $"Loop-filter={frameHeader.LoopFilterParameters.FilterLevel[0]}/{frameHeader.LoopFilterParameters.FilterLevel[1]}, "
+ $"sharpness={frameHeader.LoopFilterParameters.SharpnessLevel}, delta-q={frameHeader.DeltaQParameters.IsPresent}, "
+ $"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}, "
+ $"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}, "
+ $"left={leftModeInfo.BlockSize}/{leftModeInfo.YMode}/skip={leftModeInfo.Skip}, "
+ $"next-row={nextRowModeInfo.BlockSize}/{nextRowModeInfo.YMode}/skip={nextRowModeInfo.Skip}/"
+ $"angle-delta={nextRowModeInfo.GetAngleDelta(plane)}/transforms={nextRowModeInfo.GetTransformUnitCount(plane)}.");
}
}
}

36
tests/ImageSharp.Tests/TestImages.cs

@ -1365,6 +1365,42 @@ public static class TestImages
public const string Av1Cdef12BitReference = "Heif/Av1/Conformance/libaom-cdef-cosmos-12b-libaom.yuv";
public const string Av1Cdef12BitAvif = "Heif/Av1/Conformance/libavif-cdef-cosmos-12b.avif";
public const string Av1Cdef12BitPresentationReference = "Heif/Av1/Conformance/libavif-cdef-cosmos-12b.png";
public const string Av1Profile8BitMonochromeAvif = "Heif/Av1/Conformance/libavif-profile-8b-400.avif";
public const string Av1Profile8BitMonochromeReference = "Heif/Av1/Conformance/libavif-profile-8b-400-libaom-y4m.yuv";
public const string Av1Profile8BitMonochromePresentationReference = "Heif/Av1/Conformance/libavif-profile-8b-400.png";
public const string Av1Profile8Bit420Avif = "Heif/Av1/Conformance/libavif-profile-8b-420.avif";
public const string Av1Profile8Bit420Reference = "Heif/Av1/Conformance/libavif-profile-8b-420-libaom-y4m.yuv";
public const string Av1Profile8Bit420PresentationReference = "Heif/Av1/Conformance/libavif-profile-8b-420.png";
public const string Av1Profile8Bit422Avif = "Heif/Av1/Conformance/libavif-profile-8b-422.avif";
public const string Av1Profile8Bit422Reference = "Heif/Av1/Conformance/libavif-profile-8b-422-libaom-y4m.yuv";
public const string Av1Profile8Bit422PresentationReference = "Heif/Av1/Conformance/libavif-profile-8b-422.png";
public const string Av1Profile8Bit444Avif = "Heif/Av1/Conformance/libavif-profile-8b-444.avif";
public const string Av1Profile8Bit444Reference = "Heif/Av1/Conformance/libavif-profile-8b-444-libaom-y4m.yuv";
public const string Av1Profile8Bit444PresentationReference = "Heif/Av1/Conformance/libavif-profile-8b-444.png";
public const string Av1Profile10BitMonochromeAvif = "Heif/Av1/Conformance/libavif-profile-10b-400.avif";
public const string Av1Profile10BitMonochromeReference = "Heif/Av1/Conformance/libavif-profile-10b-400-libaom-y4m.yuv";
public const string Av1Profile10BitMonochromePresentationReference = "Heif/Av1/Conformance/libavif-profile-10b-400.png";
public const string Av1Profile10Bit420Avif = "Heif/Av1/Conformance/libavif-profile-10b-420.avif";
public const string Av1Profile10Bit420Reference = "Heif/Av1/Conformance/libavif-profile-10b-420-libaom-y4m.yuv";
public const string Av1Profile10Bit420PresentationReference = "Heif/Av1/Conformance/libavif-profile-10b-420.png";
public const string Av1Profile10Bit422Avif = "Heif/Av1/Conformance/libavif-profile-10b-422.avif";
public const string Av1Profile10Bit422Reference = "Heif/Av1/Conformance/libavif-profile-10b-422-libaom-y4m.yuv";
public const string Av1Profile10Bit422PresentationReference = "Heif/Av1/Conformance/libavif-profile-10b-422.png";
public const string Av1Profile10Bit444Avif = "Heif/Av1/Conformance/libavif-profile-10b-444.avif";
public const string Av1Profile10Bit444Reference = "Heif/Av1/Conformance/libavif-profile-10b-444-libaom-y4m.yuv";
public const string Av1Profile10Bit444PresentationReference = "Heif/Av1/Conformance/libavif-profile-10b-444.png";
public const string Av1Profile12BitMonochromeAvif = "Heif/Av1/Conformance/libavif-profile-12b-400.avif";
public const string Av1Profile12BitMonochromeReference = "Heif/Av1/Conformance/libavif-profile-12b-400-libaom-y4m.yuv";
public const string Av1Profile12BitMonochromePresentationReference = "Heif/Av1/Conformance/libavif-profile-12b-400.png";
public const string Av1Profile12Bit420Avif = "Heif/Av1/Conformance/libavif-profile-12b-420.avif";
public const string Av1Profile12Bit420Reference = "Heif/Av1/Conformance/libavif-profile-12b-420-libaom-y4m.yuv";
public const string Av1Profile12Bit420PresentationReference = "Heif/Av1/Conformance/libavif-profile-12b-420.png";
public const string Av1Profile12Bit422Avif = "Heif/Av1/Conformance/libavif-profile-12b-422.avif";
public const string Av1Profile12Bit422Reference = "Heif/Av1/Conformance/libavif-profile-12b-422-libaom-y4m.yuv";
public const string Av1Profile12Bit422PresentationReference = "Heif/Av1/Conformance/libavif-profile-12b-422.png";
public const string Av1Profile12Bit444Avif = "Heif/Av1/Conformance/libavif-profile-12b-444.avif";
public const string Av1Profile12Bit444Reference = "Heif/Av1/Conformance/libavif-profile-12b-444-libaom-y4m.yuv";
public const string Av1Profile12Bit444PresentationReference = "Heif/Av1/Conformance/libavif-profile-12b-444.png";
public const string Av1Palette8BitPayload = "Heif/Av1/Conformance/libaom-palette-draw-points-8b-444.bit";
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";

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-400-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1c71bca8da6ee62c42e1ebdcfcba89e2829f8fd9569d3bf8107c8321ed085b06
size 262220

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-400.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:78d25037c2c41f387e74a88ed844fe9888f64e88f8155af31ccf994f5566c23d
size 1631

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-400.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5ed0fd0f21b2c5620b7b6289cf3ebdd6001e8498ce7b0daec17d5657051f9cea
size 5224

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-420-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:39b2dca5c922a09a085b798fdf9cad0ce218418eb0fd33a08388ff05e6dd3114
size 393295

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-420.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5df732dc3c72684f35dfa9c69781133e6383ae8406e66e57d8cc3fa5e0dd852d
size 1682

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-420.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:649c9fef1d87858cc6b49b7fcf51ad691ea22f0a513cd69ab61a3bf1943dd1bf
size 9635

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-422-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:79c6e4560af81fe427bc38f1fa64ee43622d1ec02a7876a533ccb6be72955a9e
size 524367

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-422.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3420ea3f3fb2430ffa502a85190e3aab9aafdeb24880748d023b3a5091943c6d
size 1685

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-422.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c1a70c907c1040fcb7b2a6618fb0f9c84a4ddfdf441238138f9f3349fb79696
size 9573

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-444-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ad9f1b3318f9795f325a2f8d4e6dd5e5ea2c4ea51713f6aa9dab302a970b25bb
size 786511

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-444.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:354e339eaf0e6e2889659c2a889d65b9bdf6022543fb9f9522d11301251e0075
size 1682

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-10b-444.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:649c9fef1d87858cc6b49b7fcf51ad691ea22f0a513cd69ab61a3bf1943dd1bf
size 9635

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-400-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a020ff72013124975caae5823eaaa1abd3ed66e19cf6ccb31ff96fcd0b63a084
size 262220

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-400.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:72af196f86395af9b5939598be97c45cd065ad4b62c276a0486bf76a6568020b
size 1643

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-400.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:53c0c2e538c85323fceee54d30b7ba29d2af5fd9770a617faceadb9f2a502b2c
size 5459

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-420-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0b3b67cc8fff91e5302f02f4718967b9a2871b321109defdafb85dd46b558876
size 393295

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-420.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:063d1980df27ae7ff996c437d962ca51d4615d6f745eca6cc55a5c57a05c761d
size 1688

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-420.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:589a61003f368ce59defd0e2334b5d50bfa24119e488bea19c56542bcfc4e930
size 10122

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-422-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:efbb14ce47121971afca860b811b34da9414fb961a73e2f16fd403d835cf89d1
size 524367

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-422.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:80d606728693f5f890fc1ffb7a2bcff1b2128b783143445fdbed89da30b1353f
size 1686

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-422.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c86a0e37069641c3e077428f2a0632081ef3b8fe997e10721347377ad98d14f
size 10142

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-444-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:43907add76f881850cfdc4ed23655dc71443c8d40ef16093b51745650dc66307
size 786511

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-444.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f35443d16382c2d184adc3e518230ce4df497a429d101c92b96bb592b50c1a36
size 1683

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-12b-444.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:13b3e95816e3b98016fc7b8c11e161a3204e0dcf9a38008254d1468ec7e73769
size 9942

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-400-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:da90f960e2297224b621ff866353d89ddb0dd9ed0ffcb201d4b5e49c250c5c07
size 131146

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-400.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1b33f3d2f20a4d1d828ee3ed44e94e0ac5a13caa3971e0279ba740581437c194
size 1665

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-400.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:260a860d1f01e56c0154a31a6f4802fd27b5f507f724d55084f5278b388003c2
size 5294

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-420-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:98342115d81519b685c3c3d4077e74896b5c3ebc6eb44ccfe11dd20f43fc06df
size 196689

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-420.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:16353df2ed0b1e5f3b5111e6f514e8ba56b81be8b20aae5b4d07ccf71f203538
size 1718

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-420.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2b39c31c012ad7e194870fcdd074a83f225dc0dfc40731f0c72fa8847821e0fd
size 9631

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-422-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dd25de31e7c3175b8854cdf63ce233d2657b07ba038158286763c19ab4ca60cc
size 262217

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-422.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b1f8d6ae7131d2596f43ac9a67c0afce6f4cb4eacd323383a22073bcb8afc05a
size 1736

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-422.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:253291bc0bcebde6cc0ef6729c52ec025cba4a7ed046a3231297da7c63df3f4a
size 9748

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-444-libaom-y4m.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dae8878737e0afcc7084a252f5963bc8915e0febbf7a92284bad14bb8723e826
size 393289

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-444.avif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ef6c09e4c7b3ecfcc7fef52abe3eab6afe422ec6bf9fd0fd3af00db445384a6f
size 1722

3
tests/Images/Input/Heif/Av1/Conformance/libavif-profile-8b-444.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:96423c89e68f6d4e6a349e0693b1c0b106055654681d51c822ab43fd12b86223
size 9703
Loading…
Cancel
Save