Browse Source

Document HEIF mode decision geometry

pull/2633/head
James Jackson-South 1 week ago
parent
commit
f3641ede18
  1. 68
      src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometry.cs
  2. 114
      src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometryFactory.cs
  3. 34
      src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1GeometryIndex.cs

68
src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometry.cs

@ -5,11 +5,24 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision; namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision;
/// <summary>
/// Describes the spatial, chroma, and transform layout of one AV1 block considered by mode-decision scanning.
/// </summary>
internal class Av1BlockGeometry internal class Av1BlockGeometry
{ {
/// <summary>
/// The luma block size from which the cached luma dimensions are derived.
/// </summary>
private Av1BlockSize blockSize; private Av1BlockSize blockSize;
/// <summary>
/// The chroma block size from which the cached chroma dimensions are derived.
/// </summary>
private Av1BlockSize blockSizeUv; private Av1BlockSize blockSizeUv;
/// <summary>
/// Initializes a new instance of the <see cref="Av1BlockGeometry"/> class with storage for every supported transform depth.
/// </summary>
public Av1BlockGeometry() public Av1BlockGeometry()
{ {
this.RedunancyList = []; this.RedunancyList = [];
@ -20,6 +33,9 @@ internal class Av1BlockGeometry
} }
} }
/// <summary>
/// Gets or sets the luma block size and updates <see cref="BlockWidth"/> and <see cref="BlockHeight"/> to match.
/// </summary>
public Av1BlockSize BlockSize public Av1BlockSize BlockSize
{ {
get => this.blockSize; get => this.blockSize;
@ -31,6 +47,9 @@ internal class Av1BlockGeometry
} }
} }
/// <summary>
/// Gets or sets the chroma block size and updates <see cref="BlockWidthUv"/> and <see cref="BlockHeightUv"/> to match.
/// </summary>
public Av1BlockSize BlockSizeUv public Av1BlockSize BlockSizeUv
{ {
get => this.blockSizeUv; get => this.blockSizeUv;
@ -43,42 +62,57 @@ internal class Av1BlockGeometry
} }
/// <summary> /// <summary>
/// Gets or sets the Origin point from lop left of the superblock. /// Gets or sets the block origin in pixels relative to the top-left corner of its superblock.
/// </summary> /// </summary>
public Point Origin { get; internal set; } public Point Origin { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether this luma block owns chroma samples in the mode-decision layout.
/// </summary>
public bool HasUv { get; internal set; } public bool HasUv { get; internal set; }
/// <summary> /// <summary>
/// Gets the blocks width. /// Gets the luma block width in pixels.
/// </summary> /// </summary>
public int BlockWidth { get; private set; } public int BlockWidth { get; private set; }
/// <summary> /// <summary>
/// Gets the blocks height. /// Gets the luma block height in pixels.
/// </summary> /// </summary>
public int BlockHeight { get; private set; } public int BlockHeight { get; private set; }
/// <summary>
/// Gets the number of luma transform blocks at each transform depth.
/// </summary>
public int[] TransformBlockCount { get; } = new int[Av1Constants.MaxVarTransform + 1]; public int[] TransformBlockCount { get; } = new int[Av1Constants.MaxVarTransform + 1];
/// <summary>
/// Gets the luma transform size selected at each transform depth.
/// </summary>
public Av1TransformSize[] TransformSize { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1]; public Av1TransformSize[] TransformSize { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1];
/// <summary>
/// Gets the chroma transform size selected at each transform depth.
/// </summary>
public Av1TransformSize[] TransformSizeUv { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1]; public Av1TransformSize[] TransformSizeUv { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1];
/// <summary>
/// Gets the pixel origins of the transform blocks at each transform depth.
/// </summary>
public Point[][] TransformOrigin { get; private set; } public Point[][] TransformOrigin { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the blocks index in the Mode Decision scan. /// Gets or sets the block index in mode-decision scan order.
/// </summary> /// </summary>
public int ModeDecisionIndex { get; set; } public int ModeDecisionIndex { get; set; }
/// <summary> /// <summary>
/// Gets or sets the offset to the next nsq block (skip remaining d2 blocks). /// Gets or sets the scan offset from this square block to the next block at the same depth.
/// </summary> /// </summary>
public int NextDepthOffset { get; set; } public int NextDepthOffset { get; set; }
/// <summary> /// <summary>
/// Gets or sets the offset to the next d1 sq block /// Gets or sets the scan offset from this square block to its first child at the next depth.
/// </summary> /// </summary>
public int Depth1Offset { get; set; } public int Depth1Offset { get; set; }
@ -88,24 +122,42 @@ internal class Av1BlockGeometry
public bool IsRedundant => this.RedunancyList.Count > 0; public bool IsRedundant => this.RedunancyList.Count > 0;
/// <summary> /// <summary>
/// Gets or sets the list where the block is redundant. /// Gets or sets the mode-decision indices of blocks with the same size and origin as this block.
/// </summary> /// </summary>
public List<int> RedunancyList { get; internal set; } public List<int> RedunancyList { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the non square index within a partition 0..totns-1 /// Gets or sets the zero-based component index of this block within a non-square partition.
/// </summary> /// </summary>
public int NonSquareIndex { get; internal set; } public int NonSquareIndex { get; internal set; }
/// <summary>
/// Gets or sets the number of component blocks produced by this partition shape.
/// </summary>
public int TotalNonSuareCount { get; internal set; } public int TotalNonSuareCount { get; internal set; }
/// <summary>
/// Gets the chroma block width in pixels.
/// </summary>
public int BlockWidthUv { get; private set; } public int BlockWidthUv { get; private set; }
/// <summary>
/// Gets the chroma block height in pixels.
/// </summary>
public int BlockHeightUv { get; private set; } public int BlockHeightUv { get; private set; }
/// <summary>
/// Gets or sets the quadtree depth of this block within its superblock.
/// </summary>
public int Depth { get; internal set; } public int Depth { get; internal set; }
/// <summary>
/// Gets or sets the width and height, in pixels, of the square sequence region that produced this block.
/// </summary>
public int SequenceSize { get; internal set; } public int SequenceSize { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether this block belongs to the last quadrant of its parent.
/// </summary>
public bool IsLastQuadrant { get; internal set; } public bool IsLastQuadrant { get; internal set; }
} }

114
src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometryFactory.cs

@ -5,10 +5,24 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision; namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision;
/// <summary>
/// Builds the AV1 block and transform geometries traversed by the mode-decision scan.
/// </summary>
internal class Av1BlockGeometryFactory internal class Av1BlockGeometryFactory
{ {
/// <summary>
/// The number of scan entries required by the largest supported 128-pixel superblock geometry.
/// </summary>
private const int MaxBlocksAllocated = 4421; private const int MaxBlocksAllocated = 4421;
/// <summary>
/// Marks a geometry-depth combination that has no valid scan offset.
/// </summary>
private const int NotUsedValue = 0; private const int NotUsedValue = 0;
/// <summary>
/// Maps each partition shape, axis, and component to its origin offset measured in quarter-block units.
/// </summary>
private static readonly int[][][] NonSkipQuarterOffMult = private static readonly int[][][] NonSkipQuarterOffMult =
[ [
@ -27,6 +41,9 @@ internal class Av1BlockGeometryFactory
/*P=6*/ [[0, 2, 2, 9], [0, 0, 2, 9]] /*P=6*/ [[0, 2, 2, 9], [0, 0, 2, 9]]
]; ];
/// <summary>
/// Maps each partition shape, axis, and component to its dimension measured in quarter-block units.
/// </summary>
private static readonly uint[][][] NonSkipSizeMult = private static readonly uint[][][] NonSkipSizeMult =
[ [
@ -45,7 +62,9 @@ internal class Av1BlockGeometryFactory
/*P=6*/ [[2, 2, 2, 9], [4, 2, 2, 9]] /*P=6*/ [[2, 2, 2, 9], [4, 2, 2, 9]]
]; ];
// gives the index of next quadrant child within a depth /// <summary>
/// Maps geometry and quadtree depth to the scan offset of the next quadrant at that depth.
/// </summary>
private static readonly int[][] NonSkipDepthOffset = private static readonly int[][] NonSkipDepthOffset =
[ [
[85, 21, 5, 1, NotUsedValue, NotUsedValue], [85, 21, 5, 1, NotUsedValue, NotUsedValue],
@ -59,7 +78,9 @@ internal class Av1BlockGeometryFactory
[2377, 593, 145, 33, 5, NotUsedValue] [2377, 593, 145, 33, 5, NotUsedValue]
]; ];
// gives the next depth block(first qudrant child) from a given parent square /// <summary>
/// Maps geometry and quadtree depth to the scan offset of the square block's first child.
/// </summary>
private static readonly int[][] Depth1DepthOffset = private static readonly int[][] Depth1DepthOffset =
[ [
[1, 1, 1, 1, 1, NotUsedValue], [1, 1, 1, 1, 1, NotUsedValue],
@ -73,24 +94,47 @@ internal class Av1BlockGeometryFactory
[5, 13, 13, 13, 5, NotUsedValue] [5, 13, 13, 13, 5, NotUsedValue]
]; ];
/// <summary>
/// The geometry whose lookup-table row is active while a scan is constructed.
/// </summary>
private static Av1GeometryIndex geometryIndex; private static Av1GeometryIndex geometryIndex;
/// <summary>
/// The active geometry's superblock width and height in pixels.
/// </summary>
private static int maxSuperblock; private static int maxSuperblock;
/// <summary>
/// The number of quadtree depths generated for the active geometry.
/// </summary>
private static int maxDepth; private static int maxDepth;
/// <summary>
/// The number of partition shapes considered by the active geometry before size-specific restrictions.
/// </summary>
private static int maxPart; private static int maxPart;
// private static int maxActiveBlockCount; // private static int maxActiveBlockCount;
/// <summary>
/// Stores block geometries by mode-decision scan index.
/// </summary>
private readonly Av1BlockGeometry[] blockGeometryModeDecisionScan; private readonly Av1BlockGeometry[] blockGeometryModeDecisionScan;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Av1BlockGeometryFactory"/> class. /// Initializes a new instance of the <see cref="Av1BlockGeometryFactory"/> class.
/// </summary> /// </summary>
/// <remarks>SVT: md_scan_all_blks</remarks> /// <param name="geom">The predefined geometry used to size and populate the mode-decision scan.</param>
/// <remarks>SVT-AV1: <c>md_scan_all_blks</c>.</remarks>
public Av1BlockGeometryFactory(Av1GeometryIndex geom) public Av1BlockGeometryFactory(Av1GeometryIndex geom)
{ {
this.blockGeometryModeDecisionScan = new Av1BlockGeometry[MaxBlocksAllocated]; this.blockGeometryModeDecisionScan = new Av1BlockGeometry[MaxBlocksAllocated];
int max_block_count; int max_block_count;
geometryIndex = geom; geometryIndex = geom;
byte min_nsq_bsize; byte min_nsq_bsize;
// These preset limits and the enum order form the row index contract for the offset tables above.
// Changing one without the other would make parent and sibling scan offsets refer to a different geometry.
if (geom == Av1GeometryIndex.Geometry0) if (geom == Av1GeometryIndex.Geometry0)
{ {
maxSuperblock = 64; maxSuperblock = 64;
@ -176,8 +220,11 @@ internal class Av1BlockGeometryFactory
} }
/// <summary> /// <summary>
/// SVT: count_total_num_of_active_blks /// Counts the block entries produced by every enabled partition at every depth of the active geometry.
/// </summary> /// </summary>
/// <param name="min_nsq_bsize">The smallest square size, in pixels, at which non-square partitions remain enabled.</param>
/// <returns>The number of active mode-decision scan entries.</returns>
/// <remarks>SVT-AV1: <c>count_total_num_of_active_blks</c>.</remarks>
private static int CountTotalNumberOfActiveBlocks(int min_nsq_bsize) private static int CountTotalNumberOfActiveBlocks(int min_nsq_bsize)
{ {
int depth_scan_idx = 0; int depth_scan_idx = 0;
@ -185,12 +232,17 @@ internal class Av1BlockGeometryFactory
for (int depthIterator = 0; depthIterator < maxDepth; depthIterator++) for (int depthIterator = 0; depthIterator < maxDepth; depthIterator++)
{ {
int totalSquareCount = 1 << depthIterator; int totalSquareCount = 1 << depthIterator;
// Each quadtree depth halves the square sequence dimension. The final branch covers the deepest
// 128-pixel-superblock geometry, whose sixth level contains 4-pixel squares.
int sequenceSize = depthIterator == 0 ? maxSuperblock int sequenceSize = depthIterator == 0 ? maxSuperblock
: depthIterator == 1 ? maxSuperblock / 2 : depthIterator == 1 ? maxSuperblock / 2
: depthIterator == 2 ? maxSuperblock / 4 : depthIterator == 2 ? maxSuperblock / 4
: depthIterator == 3 ? maxSuperblock / 8 : depthIterator == 3 ? maxSuperblock / 8
: depthIterator == 4 ? maxSuperblock / 16 : maxSuperblock / 32; : depthIterator == 4 ? maxSuperblock / 16 : maxSuperblock / 32;
// AV1 restricts the partition shapes allowed at the largest and smallest block sizes. Apply those
// caps before walking the shape table so a row is never interpreted for an illegal block size.
int max_part_updated = sequenceSize == 128 ? Math.Min(maxPart, maxPart < 9 && maxPart > 3 ? 3 : 7) int max_part_updated = sequenceSize == 128 ? Math.Min(maxPart, maxPart < 9 && maxPart > 3 ? 3 : 7)
: sequenceSize == 8 ? Math.Min(maxPart, 3) : sequenceSize == 8 ? Math.Min(maxPart, 3)
: sequenceSize == 4 ? 1 : maxPart; : sequenceSize == 4 ? 1 : maxPart;
@ -216,8 +268,12 @@ internal class Av1BlockGeometryFactory
} }
/// <summary> /// <summary>
/// SVT: get_num_ns_per_part /// Gets the number of component blocks emitted by one partition shape.
/// </summary> /// </summary>
/// <param name="partitionIterator">The zero-based partition-shape index in scan order.</param>
/// <param name="sequenceSize">The width and height, in pixels, of the square being partitioned.</param>
/// <returns>The number of component blocks in the partition.</returns>
/// <remarks>SVT-AV1: <c>get_num_ns_per_part</c>.</remarks>
private static int GetNonSquareCountPerPart(int partitionIterator, int sequenceSize) private static int GetNonSquareCountPerPart(int partitionIterator, int sequenceSize)
{ {
int tot_num_ns_per_part = partitionIterator < 1 ? 1 : partitionIterator < 3 ? 2 : partitionIterator < 5 && sequenceSize < 128 ? 4 : 3; int tot_num_ns_per_part = partitionIterator < 1 ? 1 : partitionIterator < 3 ? 2 : partitionIterator < 5 && sequenceSize < 128 ? 4 : 3;
@ -225,8 +281,10 @@ internal class Av1BlockGeometryFactory
} }
/// <summary> /// <summary>
/// SVT: log_redundancy_similarity /// Records scan entries that represent the same block size at the same pixel origin.
/// </summary> /// </summary>
/// <param name="max_block_count">The number of populated scan entries to compare.</param>
/// <remarks>SVT-AV1: <c>log_redundancy_similarity</c>.</remarks>
private static void LogRedundancySimilarity(int max_block_count) private static void LogRedundancySimilarity(int max_block_count)
{ {
for (int blockIterator = 0; blockIterator < max_block_count; blockIterator++) for (int blockIterator = 0; blockIterator < max_block_count; blockIterator++)
@ -252,10 +310,24 @@ internal class Av1BlockGeometryFactory
} }
/// <summary> /// <summary>
/// SVT: get_blk_geom_mds /// Gets the block geometry at a mode-decision scan index.
/// </summary> /// </summary>
/// <param name="modeDecisionScanIndex">The zero-based mode-decision scan index.</param>
/// <returns>The geometry stored at <paramref name="modeDecisionScanIndex"/>.</returns>
/// <exception cref="NotImplementedException">Always thrown because the geometry lookup has not been implemented.</exception>
/// <remarks>SVT-AV1: <c>get_blk_geom_mds</c>.</remarks>
public static Av1BlockGeometry GetBlockGeometryByModeDecisionScanIndex(int modeDecisionScanIndex) => throw new NotImplementedException(); public static Av1BlockGeometry GetBlockGeometryByModeDecisionScanIndex(int modeDecisionScanIndex) => throw new NotImplementedException();
/// <summary>
/// Appends every enabled partition and transform layout for a square region to scan order.
/// </summary>
/// <param name="index">The next scan index; advanced once for every emitted block geometry.</param>
/// <param name="sequenceSize">The width and height, in pixels, of the square region being partitioned.</param>
/// <param name="x">The region's horizontal origin in pixels relative to the superblock.</param>
/// <param name="y">The region's vertical origin in pixels relative to the superblock.</param>
/// <param name="isLastQuadrant">Whether the region is the final quadrant of its parent.</param>
/// <param name="quadIterator">The zero-based quadrant index within the parent.</param>
/// <param name="minNonSquareBlockSize">The smallest square size, in pixels, at which non-square partitions remain enabled.</param>
private void ScanAllBlocks(ref int index, int sequenceSize, int x, int y, bool isLastQuadrant, byte quadIterator, byte minNonSquareBlockSize) private void ScanAllBlocks(ref int index, int sequenceSize, int x, int y, bool isLastQuadrant, byte quadIterator, byte minNonSquareBlockSize)
{ {
// The input block is the parent square block of size sq_size located at pos (x,y) // The input block is the parent square block of size sq_size located at pos (x,y)
@ -264,6 +336,8 @@ internal class Av1BlockGeometryFactory
int halfsize = sequenceSize / 2; int halfsize = sequenceSize / 2;
int quartsize = sequenceSize / 4; int quartsize = sequenceSize / 4;
// AV1 removes partition shapes that cannot be represented at 128-, 8-, and 4-pixel square sizes.
// The scan tables are ordered by the remaining shape set, so the cap must be applied before indexing them.
int max_part_updated = sequenceSize == 128 ? Math.Min(maxPart, maxPart is < 9 and > 3 ? 3 : 7) int max_part_updated = sequenceSize == 128 ? Math.Min(maxPart, maxPart is < 9 and > 3 ? 3 : 7)
: sequenceSize == 8 ? Math.Min(maxPart, 3) : sequenceSize == 8 ? Math.Min(maxPart, 3)
: sequenceSize == 4 ? 1 : maxPart; : sequenceSize == 4 ? 1 : maxPart;
@ -280,6 +354,8 @@ internal class Av1BlockGeometryFactory
for (int nonSquareIterator = 0; nonSquareIterator < tot_num_ns_per_part; nonSquareIterator++) for (int nonSquareIterator = 0; nonSquareIterator < tot_num_ns_per_part; nonSquareIterator++)
{ {
// Geometry presets use power-of-two superblocks, so the current square dimension uniquely identifies
// its quadtree depth without carrying recursion state in every scan entry.
this.blockGeometryModeDecisionScan[index].Depth = sequenceSize == maxSuperblock / 1 ? 0 this.blockGeometryModeDecisionScan[index].Depth = sequenceSize == maxSuperblock / 1 ? 0
: sequenceSize == maxSuperblock / 2 ? 1 : sequenceSize == maxSuperblock / 2 ? 1
: sequenceSize == maxSuperblock / 4 ? 2 : sequenceSize == maxSuperblock / 4 ? 2
@ -315,6 +391,8 @@ internal class Av1BlockGeometryFactory
this.blockGeometryModeDecisionScan[index].NonSquareIndex = nonSquareIterator; this.blockGeometryModeDecisionScan[index].NonSquareIndex = nonSquareIterator;
uint blockWidth = (uint)quartsize * NonSkipSizeMult[part_it_idx][0][nonSquareIterator]; uint blockWidth = (uint)quartsize * NonSkipSizeMult[part_it_idx][0][nonSquareIterator];
uint blockHeight = (uint)quartsize * NonSkipSizeMult[part_it_idx][1][nonSquareIterator]; uint blockHeight = (uint)quartsize * NonSkipSizeMult[part_it_idx][1][nonSquareIterator];
// Av1BlockSize indexes dimensions by log2(size) - 2 because 4x4 is the smallest coded block.
this.blockGeometryModeDecisionScan[index].BlockSize = this.blockGeometryModeDecisionScan[index].BlockSize =
Av1BlockSizeExtensions.FromWidthAndHeight(Av1Math.Log2_32(blockWidth) - 2u, Av1Math.Log2_32(blockHeight) - 2u); Av1BlockSizeExtensions.FromWidthAndHeight(Av1Math.Log2_32(blockWidth) - 2u, Av1Math.Log2_32(blockHeight) - 2u);
this.blockGeometryModeDecisionScan[index].BlockSizeUv = this.blockGeometryModeDecisionScan[index].BlockSize.GetSubsampled(true, true); this.blockGeometryModeDecisionScan[index].BlockSizeUv = this.blockGeometryModeDecisionScan[index].BlockSize.GetSubsampled(true, true);
@ -323,6 +401,8 @@ internal class Av1BlockGeometryFactory
// this.blockGeometryModeDecisionScan[index].BlockHeightUv = Math.Max(4, this.blockGeometryModeDecisionScan[index].BlockHeight >> 1); // this.blockGeometryModeDecisionScan[index].BlockHeightUv = Math.Max(4, this.blockGeometryModeDecisionScan[index].BlockHeight >> 1);
this.blockGeometryModeDecisionScan[index].HasUv = true; this.blockGeometryModeDecisionScan[index].HasUv = true;
// Chroma cannot be subdivided below its minimum block dimensions. When several luma blocks map to
// the same chroma block, only the final contributing luma component owns that shared U/V geometry.
if (this.blockGeometryModeDecisionScan[index].BlockWidth == 4 && this.blockGeometryModeDecisionScan[index].BlockHeight == 4) if (this.blockGeometryModeDecisionScan[index].BlockWidth == 4 && this.blockGeometryModeDecisionScan[index].BlockHeight == 4)
{ {
this.blockGeometryModeDecisionScan[index].HasUv = isLastQuadrant; this.blockGeometryModeDecisionScan[index].HasUv = isLastQuadrant;
@ -350,7 +430,8 @@ internal class Av1BlockGeometryFactory
} }
} }
// tx_depth 1 geom settings // Transform depth zero keeps the largest legal transform. Blocks larger than AV1's 64x64 transform
// limit are represented by two or four transform blocks whose origins cover the coded block.
int tx_depth = 0; int tx_depth = 0;
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128 this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128
? 4 ? 4
@ -405,7 +486,8 @@ internal class Av1BlockGeometryFactory
tx_size_high[this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth]];*/ tx_size_high[this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth]];*/
} }
// tx_depth 1 geom settings // Transform depth one subdivides eligible luma blocks once while chroma retains its depth-zero size.
// The block-count cases below mirror the legal rectangular AV1 transform partitions.
tx_depth = 1; tx_depth = 1;
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128 this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128
? 4 ? 4
@ -695,7 +777,8 @@ internal class Av1BlockGeometryFactory
this.blockGeometryModeDecisionScan[index].tx_height_uv[tx_depth] = this.blockGeometryModeDecisionScan[index].tx_height_uv[0];*/ this.blockGeometryModeDecisionScan[index].tx_height_uv[tx_depth] = this.blockGeometryModeDecisionScan[index].tx_height_uv[0];*/
} }
// tx_depth 2 geom settings // Transform depth two performs a second subdivision. The origin tables enumerate the child
// transforms in raster order so coefficient reconstruction visits the same spatial layout.
tx_depth = 2; tx_depth = 2;
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128 this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128
@ -970,18 +1053,21 @@ internal class Av1BlockGeometryFactory
} }
/// <summary> /// <summary>
/// SVT: av1_get_tx_size /// Gets the largest legal transform size for a luma or subsampled chroma block.
/// </summary> /// </summary>
/// <param name="blockSize">The coded block size whose transform limit is requested.</param>
/// <param name="plane">The plane index, where zero selects luma and a positive value selects chroma.</param>
/// <returns>The maximum transform size for the selected plane.</returns>
/// <remarks>SVT-AV1: <c>av1_get_tx_size</c>.</remarks>
private static Av1TransformSize GetTransformSize(Av1BlockSize blockSize, int plane) private static Av1TransformSize GetTransformSize(Av1BlockSize blockSize, int plane)
{ {
// const MbModeInfo* mbmi = xd->mi[0]; // Luma uses the coded block's normative transform ceiling directly.
// if (xd->lossless[mbmi->segment_id]) return TX_4X4;
if (plane == 0) if (plane == 0)
{ {
return blockSize.GetMaximumTransformSize(); return blockSize.GetMaximumTransformSize();
} }
// const MacroblockdPlane *pd = &xd->plane[plane]; // This geometry models 4:2:0 chroma, so both chroma axes are subsampled before selecting their limit.
bool subsampling_x = plane > 0; bool subsampling_x = plane > 0;
bool subsampling_y = plane > 0; bool subsampling_y = plane > 0;
return blockSize.GetMaxUvTransformSize(subsampling_x, subsampling_y); return blockSize.GetMaxUvTransformSize(subsampling_x, subsampling_y);

34
src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1GeometryIndex.cs

@ -3,14 +3,48 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision; namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision;
/// <summary>
/// Identifies a predefined AV1 mode-decision geometry with a fixed superblock size, search depth, and partition set.
/// </summary>
internal enum Av1GeometryIndex internal enum Av1GeometryIndex
{ {
/// <summary>
/// The 64-pixel, four-depth geometry limited to square partitions.
/// </summary>
Geometry0, Geometry0,
/// <summary>
/// The 64-pixel, four-depth geometry with horizontal and vertical binary partitions down to 16 pixels.
/// </summary>
Geometry1, Geometry1,
/// <summary>
/// The 64-pixel, four-depth geometry with horizontal and vertical binary partitions down to 8 pixels.
/// </summary>
Geometry2, Geometry2,
/// <summary>
/// The 64-pixel, four-depth geometry with binary partitions at every supported size.
/// </summary>
Geometry3, Geometry3,
/// <summary>
/// The 64-pixel, five-depth geometry with binary partitions at every supported size.
/// </summary>
Geometry4, Geometry4,
/// <summary>
/// The 64-pixel, five-depth geometry that also enables four-way horizontal and vertical partitions.
/// </summary>
Geometry5, Geometry5,
/// <summary>
/// The 64-pixel, five-depth geometry that enables all supported partition shapes.
/// </summary>
Geometry6, Geometry6,
/// <summary>
/// The 128-pixel, six-depth geometry that enables all supported partition shapes.
/// </summary>
Geometry7, Geometry7,
} }

Loading…
Cancel
Save