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.