diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
index 6cf8fa432..fbf461b1b 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
@@ -1,15 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
+///
+/// Derives AV1 entropy contexts and syntax mappings shared by symbol readers and writers.
+///
internal static class Av1SymbolContextHelper
{
+ ///
+ /// Maps each transform set and transform type to its coded symbol index.
+ ///
public static readonly int[][] ExtendedTransformIndices = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // DCT only
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Inter set 3
@@ -19,11 +24,13 @@ internal static class Av1SymbolContextHelper
[7, 8, 9, 12, 10, 11, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6], // All 16, inter set 1
];
- // Maps tx set types to the distribution indices. INTRA values only
+ ///
+ /// Maps intra transform-set types to their transform-type distribution indices.
+ ///
private static readonly int[] ExtendedTransformSetToIndex = [0, -1, 2, 1, -1, -1];
///
- /// Section 5.11.48: Transform type syntax
+ /// Maps a coded transform-type symbol back to its transform type for each intra transform set.
///
public static readonly Av1TransformType[][] ExtendedTransformInverse = [
[Av1TransformType.DctDct], // DCT only
@@ -34,8 +41,19 @@ internal static class Av1SymbolContextHelper
[], // All 16, inter set 1
];
+ ///
+ /// Defines the number of extra offset bits associated with each end-of-block token.
+ ///
public static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+
+ ///
+ /// Defines the first coefficient position represented by each end-of-block token.
+ ///
public static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513];
+
+ ///
+ /// Maps end-of-block positions below 33 directly to their token.
+ ///
private static readonly byte[] EndOfBlockToPositionSmall = [
0, 1, 2, // 0-2
3, 3, // 3-4
@@ -44,6 +62,9 @@ internal static class Av1SymbolContextHelper
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 // 17-32
];
+ ///
+ /// Maps groups of 32 larger end-of-block positions to their token.
+ ///
private static readonly byte[] EndOfBlockToPositionLarge = [
6, // place holder
7, // 33-64
@@ -64,9 +85,20 @@ internal static class Av1SymbolContextHelper
11 // 513-
];
+ ///
+ /// Reduces a rectangular transform size to the square context used by transform-size distributions.
+ ///
+ /// The coded transform size.
+ /// The square transform-size context.
internal static Av1TransformSize GetTransformSizeContext(Av1TransformSize originalSize)
=> (Av1TransformSize)(((int)originalSize.GetSquareSize() + (int)originalSize.GetSquareUpSize() + 1) >> 1);
+ ///
+ /// Reconstructs an end-of-block coefficient position from its token and extra offset.
+ ///
+ /// The decoded end-of-block token.
+ /// The decoded offset within the token group.
+ /// The one-based end-of-block coefficient position.
internal static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra)
{
int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint];
@@ -79,8 +111,11 @@ internal static class Av1SymbolContextHelper
}
///
- /// SVT: get_lower_levels_ctx_eob
+ /// Derives the lower-level context for the final nonzero coefficient.
///
+ /// The padded coefficient-level buffer.
+ /// The coefficient position in raster order.
+ /// The end-of-block lower-level context.
internal static int GetLowerLevelContextEndOfBlock(Av1LevelBuffer levels, Point position)
{
if (position.X == 0 && position.Y == 0)
@@ -104,8 +139,12 @@ internal static class Av1SymbolContextHelper
}
///
- /// SVT: get_lower_levels_ctx_2d
+ /// Derives a two-dimensional lower-level context from five forward coefficient neighbors.
///
+ /// The padded coefficient-level buffer.
+ /// The coefficient position in raster order.
+ /// The transform size selecting the positional context offset.
+ /// The lower-level context.
internal static int GetLowerLevelsContext2d(Av1LevelBuffer levelBuffer, Point position, Av1TransformSize transformSize)
{
DebugGuard.MustBeGreaterThan(position.X + position.Y, 0, nameof(position));
@@ -128,6 +167,9 @@ internal static class Av1SymbolContextHelper
/// on the fact that {0, 1}, {1, 0}, {1, 1}, {0, 2} and {2, 0} will all be 0 in
/// the end of block case.
///
+ /// The final nonzero coefficient position.
+ /// The transform direction class.
+ /// The base-range context.
internal static int GetBaseRangeContextEndOfBlock(Point pos, Av1TransformClass transformClass)
{
if (pos.X == 0 && pos.Y == 0)
@@ -146,9 +188,13 @@ internal static class Av1SymbolContextHelper
}
///
- /// SVT: get_br_ctx
+ /// Derives a base-range context from the transform-class-specific forward neighbors.
///
/// Spec section 8.2.3, under 'coeff_br'.
+ /// The padded coefficient-level buffer.
+ /// The coefficient position in raster order.
+ /// The transform direction class.
+ /// The base-range context.
internal static int GetBaseRangeContext(Av1LevelBuffer levels, Point position, Av1TransformClass transformClass)
{
Span row0 = levels.GetRow(position.Y);
@@ -207,17 +253,19 @@ internal static class Av1SymbolContextHelper
}
///
- /// SVT: get_br_ctx_2d
+ /// Derives the two-dimensional base-range context from right, below, and below-right levels.
///
+ /// The padded coefficient-level buffer.
+ /// The coefficient position in raster order.
+ /// The two-dimensional base-range context.
internal static int GetBaseRangeContext2d(Av1LevelBuffer levels, Point position)
{
DebugGuard.MustBeGreaterThan(position.X + position.Y, 0, nameof(position));
Span row0 = levels.GetRow(position.Y);
Span row1 = levels.GetRow(position.Y + 1);
- // No need to clip quantized values to COEFF_BASE_RANGE + NUM_BASE_LEVELS
- // + 1, because we clip the overall output to 6 and the unclipped
- // quantized values will always result in an output of greater than 6.
+ // The final magnitude context is clipped to six, so clipping every source level to the AV1 base-range limit
+ // first cannot change the result.
int mag =
row0[position.X + 1] + // {0, 1}
row1[position.X] + // {1, 0}
@@ -231,6 +279,14 @@ internal static class Av1SymbolContextHelper
return mag + 14;
}
+ ///
+ /// Derives a lower-level context from the transform-class-specific nonzero-map magnitude.
+ ///
+ /// The padded coefficient-level buffer.
+ /// The coefficient position in raster order.
+ /// The coded transform size.
+ /// The transform direction class.
+ /// The lower-level coefficient context.
internal static int GetLowerLevelsContext(Av1LevelBuffer levels, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass)
{
int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass);
@@ -238,8 +294,11 @@ internal static class Av1SymbolContextHelper
}
///
- /// SVT: get_ext_tx_set_type
+ /// Selects the intra transform set permitted for a transform size and reduced-set flag.
///
+ /// The coded transform size.
+ /// Indicates whether the frame restricts transform choices.
+ /// The permitted transform set.
internal static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet)
{
Av1TransformSize squareUpSize = transformSize.GetSquareUpSize();
@@ -258,6 +317,12 @@ internal static class Av1SymbolContextHelper
return squareSize == Av1TransformSize.Size16x16 ? Av1TransformSetType.IntraSet2 : Av1TransformSetType.IntraSet1;
}
+ ///
+ /// Maps an intra prediction mode to its default transform type.
+ ///
+ /// The block prediction modes.
+ /// The luma or chroma plane category.
+ /// The transform type associated with the selected prediction mode.
internal static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType)
{
Av1PredictionMode mode = (planeType == Av1PlaneType.Y) ? modeInfo.YMode : modeInfo.UvMode;
@@ -270,8 +335,14 @@ internal static class Av1SymbolContextHelper
}
///
- /// SVT: get_nz_map_ctx
+ /// Derives the nonzero-map context for one coefficient.
///
+ /// The padded coefficient-level buffer.
+ /// The coefficient position in raster order.
+ /// Indicates that this is the final nonzero coefficient.
+ /// The coded transform size.
+ /// The transform direction class.
+ /// The nonzero-map context.
internal static sbyte GetNzMapContext(
Av1LevelBuffer levels,
Point position,
@@ -289,8 +360,14 @@ internal static class Av1SymbolContextHelper
}
///
- /// SVT: svt_av1_get_nz_map_contexts_c
+ /// Populates nonzero-map contexts for every coefficient preceding the end-of-block position.
///
+ /// The padded coefficient-level buffer.
+ /// The coefficient scan order.
+ /// The one-based end-of-block position.
+ /// The coded transform size.
+ /// The transform direction class.
+ /// The raster-indexed destination contexts.
internal static void GetNzMapContexts(
Av1LevelBuffer levels,
ReadOnlySpan scan,
@@ -308,18 +385,24 @@ internal static class Av1SymbolContextHelper
}
///
- /// SVT: get_ext_tx_types
+ /// Gets the number of transform types in a transform set.
///
+ /// The transform set.
+ /// The number of permitted transform types.
internal static int GetExtendedTransformTypeCount(Av1TransformSetType setType) => ExtendedTransformInverse[(int)setType].Length;
///
- /// SVT: get_ext_tx_set
+ /// Gets the entropy-distribution index for an intra transform set.
///
+ /// The transform set.
+ /// The distribution index, or -1 for an inter-only set.
internal static int GetExtendedTransformSet(Av1TransformSetType setType) => ExtendedTransformSetToIndex[(int)setType];
///
- /// SVT: set_dc_sign
+ /// Packs the sign of the DC coefficient into a cumulative-level context value.
///
+ /// The cumulative-level context to update.
+ /// The signed DC coefficient.
internal static void SetDcSign(ref int culLevel, int dcValue)
{
if (dcValue < 0)
@@ -333,8 +416,11 @@ internal static class Av1SymbolContextHelper
}
///
- /// SVT: get_eob_pos_token
+ /// Converts a one-based end-of-block position to its token and group offset.
///
+ /// The one-based end-of-block position.
+ /// Receives the offset within the selected token group.
+ /// The end-of-block token.
internal static short GetEndOfBlockPosition(ushort endOfBlock, out int extra)
{
short t;
@@ -352,50 +438,53 @@ internal static class Av1SymbolContextHelper
return t;
}
- public static int GetSegmentId(Av1PartitionInfo partitionInfo, ObuFrameHeader frameHeader, int[][] segmentIds, int rowIndex, int columnIndex)
- {
- int modeInfoOffset = (rowIndex * frameHeader.ModeInfoColumnCount) + columnIndex;
- int bw4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount();
- int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount();
- int xMin = Math.Min(frameHeader.ModeInfoColumnCount - columnIndex, bw4);
- int yMin = Math.Min(frameHeader.ModeInfoRowCount - rowIndex, bh4);
- int segmentId = Av1Constants.MaxSegmentCount - 1;
- for (int y = 0; y < yMin; y++)
- {
- for (int x = 0; x < xMin; x++)
- {
- segmentId = Math.Min(segmentId, segmentIds[y][x]);
- }
- }
-
- return segmentId;
- }
+ ///
+ /// Gets the decoded segment identifier at one spatial-neighbor position.
+ ///
+ /// The row-major decoded segment map.
+ /// The mode-info row.
+ /// The mode-info column.
+ /// The segment identifier stored at the requested position.
+ public static int GetSegmentId(int[][] segmentIds, int rowIndex, int columnIndex)
+ => segmentIds[rowIndex][columnIndex];
///
- /// SVT: svt_aom_get_segment_id
+ /// Gets the minimum encoded segment identifier across a block's clipped mode-info coverage.
///
- public static int GetSegmentId(Av1EncoderCommon cm, ReadOnlySpan segment_ids, Av1BlockSize bsize, Point modeInfoPosition)
+ /// The encoder frame geometry.
+ /// The row-major encoder segment map.
+ /// The block size.
+ /// The starting position in mode-info units.
+ /// The minimum segment identifier in the covered region.
+ public static int GetSegmentId(Av1EncoderCommon encoderCommon, ReadOnlySpan segmentIds, Av1BlockSize blockSize, Point modeInfoPosition)
{
- int mi_offset = (modeInfoPosition.Y * cm.ModeInfoColumnCount) + modeInfoPosition.X;
- int bw = bsize.GetWidth();
- int bh = bsize.GetHeight();
- int xmis = Math.Min(cm.ModeInfoColumnCount - modeInfoPosition.X, bw);
- int ymis = Math.Min(cm.ModeInfoRowCount - modeInfoPosition.Y, bh);
- int segment_id = Av1Constants.MaxSegmentCount;
-
- for (int y = 0; y < ymis; ++y)
+ int modeInfoOffset = (modeInfoPosition.Y * encoderCommon.ModeInfoColumnCount) + modeInfoPosition.X;
+ int blockWidth = blockSize.Get4x4WideCount();
+ int blockHeight = blockSize.Get4x4HighCount();
+ int columnCount = Math.Min(encoderCommon.ModeInfoColumnCount - modeInfoPosition.X, blockWidth);
+ int rowCount = Math.Min(encoderCommon.ModeInfoRowCount - modeInfoPosition.Y, blockHeight);
+ int segmentId = Av1Constants.MaxSegmentCount;
+
+ for (int y = 0; y < rowCount; ++y)
{
- int offset = mi_offset + (y * cm.ModeInfoColumnCount);
- for (int x = 0; x < xmis; ++x)
+ int offset = modeInfoOffset + (y * encoderCommon.ModeInfoColumnCount);
+ for (int x = 0; x < columnCount; ++x)
{
- segment_id = Math.Min(segment_id, segment_ids[offset + x]);
+ segmentId = Math.Min(segmentId, segmentIds[offset + x]);
}
}
- Guard.IsTrue(segment_id is >= 0 and < Av1Constants.MaxSegmentCount, nameof(segment_id), "Segment ID needs to be in proper range.");
- return segment_id;
+ Guard.IsTrue(segmentId is >= 0 and < Av1Constants.MaxSegmentCount, nameof(segmentId), "Segment ID needs to be in proper range.");
+ return segmentId;
}
+ ///
+ /// Reconstructs a segment identifier coded as an alternating distance from its spatial predictor.
+ ///
+ /// The coded nonnegative distance symbol.
+ /// The predicted segment identifier.
+ /// The exclusive upper bound of the segment identifier range.
+ /// The reconstructed segment identifier.
public static int NegativeDeinterleave(int diff, int reference, int max)
{
if (reference == 0)
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
index ac7c89309..d8a3fd1e3 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
@@ -1389,17 +1389,17 @@ internal class Av1TileReader : IAv1TileReader
int rowIndex = partitionInfo.RowIndex;
if (partitionInfo.AvailableAbove && partitionInfo.AvailableLeft)
{
- prevUL = Av1SymbolContextHelper.GetSegmentId(partitionInfo, this.FrameHeader, this.segmentIds, rowIndex - 1, columnIndex - 1);
+ prevUL = Av1SymbolContextHelper.GetSegmentId(this.segmentIds, rowIndex - 1, columnIndex - 1);
}
if (partitionInfo.AvailableAbove)
{
- prevU = Av1SymbolContextHelper.GetSegmentId(partitionInfo, this.FrameHeader, this.segmentIds, rowIndex - 1, columnIndex);
+ prevU = Av1SymbolContextHelper.GetSegmentId(this.segmentIds, rowIndex - 1, columnIndex);
}
if (partitionInfo.AvailableLeft)
{
- prevU = Av1SymbolContextHelper.GetSegmentId(partitionInfo, this.FrameHeader, this.segmentIds, rowIndex, columnIndex - 1);
+ prevL = Av1SymbolContextHelper.GetSegmentId(this.segmentIds, rowIndex, columnIndex - 1);
}
if (prevU == -1)
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
index eb67fabdd..c19482cbf 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
@@ -1540,17 +1540,17 @@ internal partial class Av1TileWriter
if (up_available && left_available)
{
- prev_ul = Av1SymbolContextHelper.GetSegmentId(cm, segmentation_map, Av1BlockSize.Block4x4, new Point(mi_row - 1, mi_col - 1));
+ prev_ul = Av1SymbolContextHelper.GetSegmentId(cm, segmentation_map, Av1BlockSize.Block4x4, new Point(mi_col - 1, mi_row - 1));
}
if (up_available)
{
- prev_u = Av1SymbolContextHelper.GetSegmentId(cm, segmentation_map, Av1BlockSize.Block4x4, new Point(mi_row - 1, mi_col - 0));
+ prev_u = Av1SymbolContextHelper.GetSegmentId(cm, segmentation_map, Av1BlockSize.Block4x4, new Point(mi_col, mi_row - 1));
}
if (left_available)
{
- prev_l = Av1SymbolContextHelper.GetSegmentId(cm, segmentation_map, Av1BlockSize.Block4x4, new Point(mi_row - 0, mi_col - 1));
+ prev_l = Av1SymbolContextHelper.GetSegmentId(cm, segmentation_map, Av1BlockSize.Block4x4, new Point(mi_col - 1, mi_row));
}
// The entropy context records whether zero, two, or all three neighboring IDs agree.