diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md
index 40522b3f34..9ad10babb3 100644
--- a/HEIF_IMPLEMENTATION_PLAN.md
+++ b/HEIF_IMPLEMENTATION_PLAN.md
@@ -822,6 +822,7 @@ Encoder verification contract:
- [ ] Implement real rate-distortion selection and make quality and effort change work, size, and output quality.
- [~] The tile writer now publishes one packed coefficient context per covered 4x4 edge unit and derives luma/chroma skip plus DC-sign contexts from the complete transform edges using current-libaom units. Complete tile traversal, initialized picture state, and verified CDF update behavior remain.
- [~] Encoder mode information now uses a frame-owned reference grid over its contiguous allocation, matching current libaom's `mi_grid_base` and `mi_alloc` ownership without per-block tail copies. Signed relative neighbor lookup, 4x4-unit addressing, and mutable selected skip syntax have focused contracts; complete mode decision still remains.
+- [~] Per-block transform, palette-size, and prediction syntax now uses fixed inline storage matching libaom's embedded block state. All 16 transform entries are immediately usable, directional deltas retain their signed range, and traversal performs no managed allocation; picture-level block storage and complete decision state remain.
- [ ] Implement legal deblocking, CDEF, restoration, super-resolution, and film-grain signaling decisions.
- [~] The coefficient symbol encoder now reuses tile-lifetime level and context workspaces instead of allocating per transform. Every remaining encoder fragment must be audited before it becomes active.
- [~] The planar conversion, forward transform, and forward quantizer use descending SIMD dispatch: Vector512, Vector256, Vector128, then scalar. Apply the same rule to every later hot-path family.
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs
index 21da0fca6d..facacd1f9c 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Runtime.CompilerServices;
+
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
///
@@ -8,10 +10,25 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
///
internal class Av1EncoderBlockStruct
{
+ ///
+ /// Stores transform-unit state inline with the block.
+ ///
+ private InlineArray16 transformBlocks;
+
+ ///
+ /// Stores the luma and shared chroma palette sizes inline with the block.
+ ///
+ private InlineArray2 paletteSize;
+
+ ///
+ /// Stores the block's prediction-unit syntax inline.
+ ///
+ private Av1EncoderPredictionUnit predictionUnit;
+
///
/// Gets the transform-unit state in transform traversal order.
///
- public Av1TransformUnit[] TransformBlocks { get; } = new Av1TransformUnit[Av1Constants.MaxTransformUnitCount];
+ public Span TransformBlocks => this.transformBlocks;
///
/// Gets or sets the macroblock edge and neighbor state used while writing the block.
@@ -39,12 +56,21 @@ internal class Av1EncoderBlockStruct
public Av1FilterIntraMode FilterIntraMode { get; set; }
///
- /// Gets or sets the palette size for luma and for the shared chroma mode.
+ /// Gets the writable palette sizes for luma and for the shared chroma mode.
+ ///
+ public Span PaletteSize => this.paletteSize;
+
+ ///
+ /// Gets the encoder prediction-unit state for the block.
///
- public required int[] PaletteSize { get; set; }
+ public ref Av1EncoderPredictionUnit PredictionUnit => ref this.predictionUnit;
///
- /// Gets or sets the encoder prediction-unit state for the block.
+ /// Stores the two palette-size values embedded by libaom in block mode information.
///
- public required Av1EncoderPredictionUnit[] PredictionUnits { get; set; }
+ [InlineArray(2)]
+ private struct InlineArray2
+ {
+ private T element;
+ }
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs
index aa8f078e26..39c17cb5c1 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs
@@ -1,25 +1,43 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
///
/// Stores encoder-selected intra prediction modes and directional-angle adjustments for one block.
///
-internal class Av1EncoderPredictionUnit
+internal struct Av1EncoderPredictionUnit
{
///
- /// Gets or sets the directional angle adjustment for each prediction plane.
+ /// Stores the signed luma and chroma directional adjustments.
+ ///
+ private InlineArray2 angleDelta;
+
+ ///
+ /// Gets the directional angle adjustment for each prediction plane.
///
- public required byte[] AngleDelta { get; set; }
+ [UnscopedRef]
+ public Span AngleDelta => this.angleDelta;
///
/// Gets or sets the chroma-from-luma alpha magnitude index.
///
- public int ChromaFromLumaIndex { get; set; }
+ public byte ChromaFromLumaIndex { get; set; }
///
/// Gets or sets the packed chroma-from-luma alpha signs for the U and V planes.
///
- public int ChromaFromLumaSigns { get; set; }
+ public sbyte ChromaFromLumaSigns { get; set; }
+
+ ///
+ /// Stores the two signed angle deltas embedded by libaom in block mode information.
+ ///
+ [InlineArray(Av1Constants.PlaneTypeCount)]
+ private struct InlineArray2
+ {
+ private T element;
+ }
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
index 7e1e5fdae1..e553ffa63c 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
@@ -575,14 +575,14 @@ internal partial class Av1TileWriter
if (chromaMode == Av1ChromaPredictionMode.ChromaFromLuma)
{
writer.WriteChromaFromLumaAlphas(
- blk_ptr.PredictionUnits[0].ChromaFromLumaIndex,
- blk_ptr.PredictionUnits[0].ChromaFromLumaSigns);
+ blk_ptr.PredictionUnit.ChromaFromLumaIndex,
+ blk_ptr.PredictionUnit.ChromaFromLumaSigns);
}
if (blockSize >= Av1BlockSize.Block8x8 && macroBlockModeInfo.Block.UvMode.IsDirectional())
{
writer.WriteAngleDelta(
- blk_ptr.PredictionUnits[0].AngleDelta[(int)Av1PlaneType.Uv] + Av1Constants.MaxAngleDelta,
+ blk_ptr.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv] + Av1Constants.MaxAngleDelta,
chromaMode.ToLumaMode());
}
}
@@ -632,7 +632,7 @@ internal partial class Av1TileWriter
if (blockSize >= Av1BlockSize.Block8x8 && macroBlockModeInfo.Block.Mode.IsDirectional())
{
- writer.WriteAngleDelta(blk_ptr.PredictionUnits[0].AngleDelta[(int)Av1PlaneType.Y] + Av1Constants.MaxAngleDelta, lumaMode);
+ writer.WriteAngleDelta(blk_ptr.PredictionUnit.AngleDelta[(int)Av1PlaneType.Y] + Av1Constants.MaxAngleDelta, lumaMode);
}
}
@@ -1120,15 +1120,15 @@ internal partial class Av1TileWriter
}
int tx_depth = mbmi.Block.TransformDepth;
- uint txb_count = 1;
+ int transformBlockCount = 1;
ObuFrameHeader frameHeader = pcs.Parent.FrameHeader;
- for (uint tx_index = 0; tx_index < txb_count; ++tx_index)
+ for (int transformBlockIndex = 0; transformBlockIndex < transformBlockCount; ++transformBlockIndex)
{
Av1TransformSize chromaTransformSize = blockGeometry.TransformSizeUv[tx_depth];
int transformWidth = chromaTransformSize.GetWidth();
int transformHeight = chromaTransformSize.GetHeight();
- Point transformOrigin = blockGeometry.TransformOrigin[tx_depth][tx_index];
+ Point transformOrigin = blockGeometry.TransformOrigin[tx_depth][transformBlockIndex];
Point chromaOrigin = RoundUv(blockOrigin + (Size)transformOrigin - (Size)blockGeometry.Origin) >> 1;
// Both chroma planes share transform geometry but retain independent coefficient contexts.
@@ -1139,8 +1139,8 @@ internal partial class Av1TileWriter
chromaOrigin,
blockGeometry.BlockSizeUv,
chromaTransformSize);
- Av1TransformType chromaTransformType = blk_ptr.TransformBlocks[tx_index].TransformType[(int)Av1ComponentType.Chroma];
- int endOfBlockCb = blk_ptr.TransformBlocks[tx_index].NzCoefficientCount[1];
+ Av1TransformType chromaTransformType = blk_ptr.TransformBlocks[transformBlockIndex].TransformType[(int)Av1ComponentType.Chroma];
+ int endOfBlockCb = blk_ptr.TransformBlocks[transformBlockIndex].NzCoefficientCount[1];
int culLevelCb = writer.WriteCoefficients(
chromaTransformSize,
chromaTransformType,
@@ -1153,7 +1153,7 @@ internal partial class Av1TileWriter
blk_ptr.FilterIntraMode);
coefficientBuffer = coeff_ptr.GetPlaneBuffer(Av1Plane.V).DangerousGetSingleSpan().Slice(entropyCodingContext.CodedAreaSuperblockUv);
- int endOfBlockCr = blk_ptr.TransformBlocks[tx_index].NzCoefficientCount[2];
+ int endOfBlockCr = blk_ptr.TransformBlocks[transformBlockIndex].NzCoefficientCount[2];
blockContext = GetTransformBlockContexts(
Av1ComponentType.Chroma,
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs
index 2755a37a98..c8322a1b82 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
@@ -8,15 +10,45 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
///
/// Stores the transform syntax and coefficient range for one AV1 transform unit.
///
-internal class Av1TransformUnit
+internal struct Av1TransformUnit
{
+ ///
+ /// Stores the luma, blue-difference, and red-difference end-of-block positions.
+ ///
+ private InlineArray3 nzCoefficientCount;
+
+ ///
+ /// Stores the luma and shared chroma transform types.
+ ///
+ private InlineArray2 transformType;
+
///
/// Gets the nonzero-coefficient count for each color plane.
///
- public ushort[] NzCoefficientCount { get; } = new ushort[3];
+ [UnscopedRef]
+ public Span NzCoefficientCount => this.nzCoefficientCount;
///
/// Gets the transform type selected for each color plane.
///
- public Av1TransformType[] TransformType { get; } = new Av1TransformType[Av1Constants.PlaneTypeCount];
+ [UnscopedRef]
+ public Span TransformType => this.transformType;
+
+ ///
+ /// Stores the three per-plane coefficient counts inline.
+ ///
+ [InlineArray(3)]
+ private struct InlineArray3
+ {
+ private T element;
+ }
+
+ ///
+ /// Stores the luma and shared chroma transform types inline.
+ ///
+ [InlineArray(Av1Constants.PlaneTypeCount)]
+ private struct InlineArray2
+ {
+ private T element;
+ }
}
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs
index 4270b303e4..6eace8b660 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs
@@ -129,22 +129,7 @@ public class Av1CoefficientsEntropyTests
grid[1] = above;
grid[3] = left;
grid[4] = current;
- ObuTileGroupHeader tiles = new()
- {
- TileColumnCount = 1,
- TileRowCount = 1
- };
-
- tiles.TileColumnStartModeInfo[1] = 3;
- tiles.TileRowStartModeInfo[1] = 3;
- ObuFrameHeader frameHeader = new()
- {
- ModeInfoColumnCount = 3,
- ModeInfoRowCount = 3,
- TilesInfo = tiles
- };
-
- Av1MacroBlockD macroBlock = new() { Tile = new Av1TileInfo(0, 0, frameHeader) };
+ Av1MacroBlockD macroBlock = CreateMacroBlock();
macroBlock.SetModeInfoGrid(grid, 4);
Assert.Same(left, macroBlock.GetRelativeModeInfo(-1));
@@ -180,6 +165,31 @@ public class Av1CoefficientsEntropyTests
Assert.Equal(Av1ChromaPredictionMode.Smooth, modeInfo.UvMode);
}
+ [Fact]
+ public void EncoderBlockInlineStateSupportsEveryTransformWithoutTraversalAllocations()
+ {
+ Av1EncoderBlockStruct block = new() { MacroBlock = CreateMacroBlock() };
+
+ long before = GC.GetAllocatedBytesForCurrentThread();
+ Span transforms = block.TransformBlocks;
+ transforms[^1].NzCoefficientCount[2] = 17;
+ transforms[^1].TransformType[(int)Av1PlaneType.Uv] = Av1TransformType.VerticalAdst;
+ block.PaletteSize[0] = 3;
+ block.PaletteSize[1] = 5;
+ block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Y] = -2;
+ block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv] = 3;
+ long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
+
+ Assert.Equal(Av1Constants.MaxTransformUnitCount, transforms.Length);
+ Assert.Equal(17, block.TransformBlocks[^1].NzCoefficientCount[2]);
+ Assert.Equal(Av1TransformType.VerticalAdst, block.TransformBlocks[^1].TransformType[(int)Av1PlaneType.Uv]);
+ Assert.Equal(3, block.PaletteSize[0]);
+ Assert.Equal(5, block.PaletteSize[1]);
+ Assert.Equal(-2, block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Y]);
+ Assert.Equal(3, block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv]);
+ Assert.Equal(0, allocated);
+ }
+
[Fact]
public void RoundTripZeroEndOfBlock()
{
@@ -394,6 +404,26 @@ public class Av1CoefficientsEntropyTests
}
};
+ private static Av1MacroBlockD CreateMacroBlock()
+ {
+ ObuTileGroupHeader tiles = new()
+ {
+ TileColumnCount = 1,
+ TileRowCount = 1
+ };
+
+ tiles.TileColumnStartModeInfo[1] = 3;
+ tiles.TileRowStartModeInfo[1] = 3;
+ ObuFrameHeader frameHeader = new()
+ {
+ ModeInfoColumnCount = 3,
+ ModeInfoRowCount = 3,
+ TilesInfo = tiles
+ };
+
+ return new Av1MacroBlockD { Tile = new Av1TileInfo(0, 0, frameHeader) };
+ }
+
public static TheoryData GetTransformTypes()
{
TheoryData result = [];