From 686bb21601ce706e26f99413271752c5f59ee05b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 24 Aug 2026 18:22:31 +1000 Subject: [PATCH] Decode each parsed AV1 block once --- .../Heif/Av1/Pipeline/Av1FrameDecoder.cs | 4 +- .../Formats/Heif/Av1/Tiling/Av1FrameInfo.cs | 10 +++++ .../Heif/Av1/Tiling/Av1SuperblockInfo.cs | 5 +++ .../Formats/Heif/Av1/Av1TilingTests.cs | 37 +++++++++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs index 391e26d52..9d459c6ce 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs @@ -120,9 +120,7 @@ internal class Av1FrameDecoder : IAv1FrameDecoder /// private void DecodePartition(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { - Av1BlockModeInfo modeInfo = superblockInfo.GetModeInfo(modeInfoPosition); - - for (int i = 0; i < superblockInfo.BlockCount; i++) + foreach (Av1BlockModeInfo modeInfo in superblockInfo.GetModeInfos()) { Point subPosition = modeInfo.PositionInSuperblock; Av1BlockSize subSize = modeInfo.BlockSize; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs index a61f14657..a004797c1 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs @@ -119,6 +119,16 @@ internal partial class Av1FrameInfo return this.modeInfos[index]; } + /// + /// Gets the mode information records parsed for the specified superblock in bitstream order. + /// + public Span GetModeInfos(Point superblockIndex, int count) + { + Point location = this.GetModeInfoPosition(superblockIndex, Point.Empty); + int index = this.modeInfoMap[location]; + return this.modeInfos.AsSpan(index, count); + } + public Span GetSuperblockTransform(int plane, Point index) { if (plane == 0) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs index 4f84d21a7..11a852218 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs @@ -47,6 +47,11 @@ internal class Av1SuperblockInfo public Span GetTransformInfoUv() => this.frameInfo.GetSuperblockTransformUv(this.Position); + /// + /// Gets the mode information records parsed for this superblock in bitstream order. + /// + public Span GetModeInfos() => this.frameInfo.GetModeInfos(this.Position, this.BlockCount); + public Av1BlockModeInfo GetModeInfo(Point index) => this.frameInfo.GetModeInfo(this.Position, index); public Span GetCoefficients(Av1Plane plane) => plane switch diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index bc85d6ebd..efd583760 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -112,6 +112,43 @@ public class Av1TilingTests Assert.Equal(superblockCount, frameDecoder.SuperblockCount); } + [Fact] + public void ParsedSuperblocksExposeEveryModeInfoInBitstreamOrder() + { + string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Heif.XnConvert); + byte[] content = File.ReadAllBytes(filePath); + const int dataOffset = 0x010E; + const int dataSize = 0x03CC; + const int tileOffset = 18; + Span headerSpan = content.AsSpan(dataOffset, dataSize); + Span tileSpan = content.AsSpan(tileOffset, dataSize - tileOffset); + Av1BitStreamReader bitStreamReader = new(headerSpan); + IAv1TileReader stub = new Av1TileDecoderStub(); + ObuReader obuReader = new(); + obuReader.ReadAll(ref bitStreamReader, dataSize, () => stub); + Av1TileReader tileReader = new(Configuration.Default, obuReader.SequenceHeader, obuReader.FrameHeader); + + tileReader.ReadTile(tileSpan, 0); + + int parsedModeInfoCount = 0; + int superblockSize = obuReader.SequenceHeader.SuperblockModeInfoSize; + for (int row = 0; row < obuReader.FrameHeader.ModeInfoRowCount; row += superblockSize) + { + for (int column = 0; column < obuReader.FrameHeader.ModeInfoColumnCount; column += superblockSize) + { + Point superblockPosition = new(column / superblockSize, row / superblockSize); + Av1SuperblockInfo superblockInfo = tileReader.FrameInfo.GetSuperblock(superblockPosition); + Span modeInfos = superblockInfo.GetModeInfos(); + + Assert.Equal(superblockInfo.BlockCount, modeInfos.Length); + Assert.DoesNotContain(modeInfos.ToArray(), modeInfo => modeInfo is null); + parsedModeInfoCount += modeInfos.Length; + } + } + + Assert.True(parsedModeInfoCount > 16); + } + [Theory] [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18, 16)] [InlineData(TestImages.Heif.Orange4x4, 0x010E, 0x001d, 21, 1)]