diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs index 09efc4ba9..c01fe3090 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs @@ -3,8 +3,14 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; +/// +/// Provides the normative initial AV1 cumulative distributions used before tile-local adaptation. +/// internal static class Av1DefaultDistributions { + /// + /// Gets the intra-frame luma-mode distributions indexed by block-size group. + /// public static Av1Distribution[] FrameYMode => [ new(22801, 23489, 24293, 24756, 25601, 26123, 26606, 27418, 27945, 29228, 29685, 30349), @@ -13,6 +19,9 @@ internal static class Av1DefaultDistributions new(20155, 21301, 22838, 23178, 23261, 23533, 23703, 24804, 25352, 26575, 27016, 28049) ]; + /// + /// Gets the key-frame luma-mode distributions indexed by the above and left intra-mode contexts. + /// public static Av1Distribution[][] FilterYMode => [ [ @@ -48,6 +57,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the chroma intra-mode distributions indexed first by chroma-from-luma availability and then by luma mode. + /// public static Av1Distribution[][] UvMode => [ [ @@ -81,6 +93,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the directional intra-prediction angle-delta distributions indexed by directional mode. + /// public static Av1Distribution[] AngleDelta => [ new(2180, 5032, 7567, 22776, 26989, 30217), @@ -93,8 +108,14 @@ internal static class Av1DefaultDistributions new(3605, 10428, 12459, 17676, 21244, 30655) ]; + /// + /// Gets the binary distribution that signals intra-block copy. + /// public static Av1Distribution IntraBlockCopy => new(30531); + /// + /// Gets the partition-type distributions indexed by block-size and neighboring split context. + /// public static Av1Distribution[] PartitionTypes => [ new(19132, 25510, 30392), @@ -120,25 +141,28 @@ internal static class Av1DefaultDistributions ]; /// - /// Gets the skip . + /// Gets the transform-skip distributions indexed by the neighboring skip context. /// - /// SVT: default_skip_cdfs public static Av1Distribution[] Skip => [new(31671), new(16515), new(4576)]; /// - /// Gets the skip mode . + /// Gets the skip-mode distributions indexed by the neighboring skip-mode context. /// - /// SVT: default_skip_mode_cdfs public static Av1Distribution[] SkipMode => [new(32621), new(20708), new(8127)]; + /// + /// Gets the distribution for an absolute loop-filter delta magnitude. + /// public static Av1Distribution DeltaLoopFilterAbsolute => new(28160, 32120, 32677); + /// + /// Gets the distribution for an absolute quantizer delta magnitude. + /// public static Av1Distribution DeltaQuantizerAbsolute => new(28160, 32120, 32677); /// - /// Gets the Segment identifier . + /// Gets the spatially predicted segment-identifier distributions indexed by neighboring segment context. /// - /// SVT: default_spatial_pred_seg_tree_cdf public static Av1Distribution[] SegmentId => [ new(5622, 7893, 16093, 18233, 27809, 28373, 32533), @@ -146,6 +170,9 @@ internal static class Av1DefaultDistributions new(27527, 28487, 28723, 28890, 32397, 32647, 32679), ]; + /// + /// Gets the key-frame luma intra-mode distributions indexed by the above and left mode contexts. + /// public static Av1Distribution[][] KeyFrameYMode => [ [ @@ -181,8 +208,14 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the distribution selecting a filter-intra prediction mode. + /// public static Av1Distribution FilterIntraMode => new(8949, 12776, 17211, 29558); + /// + /// Gets the binary filter-intra enable distributions indexed by block size. + /// public static Av1Distribution[] FilterIntra => [ new(4621), new(6743), new(5893), new(7866), new(12551), new(9394), @@ -191,6 +224,9 @@ internal static class Av1DefaultDistributions new(20229), new(18101), new(16384), new(16384) ]; + /// + /// Gets the transform-size distributions indexed by maximum transform category and neighboring transform-size context. + /// public static Av1Distribution[][] TransformSize => [ [new(19968), new(19968), new(24320)], @@ -199,6 +235,9 @@ internal static class Av1DefaultDistributions [new(5782, 11475), new(5782, 11475), new(16803, 22759)], ]; + /// + /// Gets the end-of-block token distributions for transforms containing at most 16 coefficients, indexed by quantizer, plane, and transform-class contexts. + /// private static Av1Distribution[][][] EndOfBlockFlagMulti16 => [ [ @@ -219,6 +258,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the end-of-block token distributions for transforms containing at most 32 coefficients, indexed by quantizer, plane, and transform-class contexts. + /// private static Av1Distribution[][][] EndOfBlockFlagMulti32 => [ [ @@ -239,6 +281,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the end-of-block token distributions for transforms containing at most 64 coefficients, indexed by quantizer, plane, and transform-class contexts. + /// private static Av1Distribution[][][] EndOfBlockFlagMulti64 => [ [ @@ -259,6 +304,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the end-of-block token distributions for transforms containing at most 128 coefficients, indexed by quantizer, plane, and transform-class contexts. + /// private static Av1Distribution[][][] EndOfBlockFlagMulti128 => [ [ @@ -279,6 +327,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the end-of-block token distributions for transforms containing at most 256 coefficients, indexed by quantizer, plane, and transform-class contexts. + /// private static Av1Distribution[][][] EndOfBlockFlagMulti256 => [ [ @@ -323,6 +374,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the end-of-block token distributions for transforms containing at most 512 coefficients, indexed by quantizer, plane, and transform-class contexts. + /// private static Av1Distribution[][][] EndOfBlockFlagMulti512 => [ [ @@ -367,6 +421,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the end-of-block token distributions for transforms containing at most 1024 coded coefficients, indexed by quantizer, plane, and transform-class contexts. + /// private static Av1Distribution[][][] EndOfBlockFlagMulti1024 => [ [ @@ -411,6 +468,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the coefficient base-range distributions indexed by quantizer, transform-size, plane, and base-range contexts. + /// private static Av1Distribution[][][][] CoefficientsBaseRange => [ [ @@ -823,6 +883,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the coefficient base-level distributions indexed by quantizer, transform-size, plane, and nonzero-map contexts. + /// private static Av1Distribution[][][][] CoefficientsBase => [ [ @@ -1515,6 +1578,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the final-nonzero coefficient base-level distributions indexed by quantizer, transform-size, plane, and end-of-block contexts. + /// private static Av1Distribution[][][][] BaseEndOfBlock => [ [ @@ -1607,6 +1673,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the DC coefficient-sign distributions indexed by quantizer, plane, and neighboring sign contexts. + /// private static Av1Distribution[][][] DcSign => [ [ @@ -1627,7 +1696,9 @@ internal static class Av1DefaultDistributions ], ]; - // SVT: av1_default_txb_skip_cdfs + /// + /// Gets the transform-block skip distributions indexed by quantizer, transform-size, and neighboring skip contexts. + /// private static Av1Distribution[][][] TransformBlockSkip => [ [ @@ -1720,6 +1791,9 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the end-of-block extra-bit distributions indexed by quantizer, transform-size, plane, and token contexts. + /// private static Av1Distribution[][][][] EndOfBlockExtra => [ [ @@ -2012,8 +2086,14 @@ internal static class Av1DefaultDistributions ] ]; + /// + /// Gets the joint chroma-from-luma sign distribution for the U and V alpha values. + /// public static Av1Distribution ChromaFromLumaSign => new(1418, 2123, 13340, 18405, 26972, 28343, 32294); + /// + /// Gets the chroma-from-luma alpha-magnitude distributions indexed by joint-sign context. + /// public static Av1Distribution[] ChromaFromLumaAlpha => [ new(7637, 20719, 31401, 32481, 32657, 32688, 32692, 32696, 32700, 32704, 32708, 32712, 32716, 32720, 32724), @@ -2024,6 +2104,9 @@ internal static class Av1DefaultDistributions new(14738, 21678, 25779, 27901, 29024, 30302, 30980, 31843, 32144, 32413, 32520, 32594, 32622, 32656, 32660) ]; + /// + /// Gets the intra transform-type distributions indexed by transform set, transform-size group, and intra prediction mode. + /// public static Av1Distribution[][][] IntraExtendedTransform => [ [ @@ -2214,8 +2297,14 @@ internal static class Av1DefaultDistributions ], ]; + /// + /// Gets the complete set of end-of-block token distributions for a base quantizer. + /// + /// The frame base quantizer index. + /// The distributions indexed by coefficient-count category, plane, and transform-class contexts. public static Av1Distribution[][][] GetEndOfBlockFlag(int baseQIndex) { + // AV1 initializes coefficient models from one of four quantizer bands, then adapts each tile's copy. int qContext = GetQContext(baseQIndex); return [ @@ -2229,24 +2318,59 @@ internal static class Av1DefaultDistributions ]; } + /// + /// Gets the coefficient base-range distributions for a base quantizer. + /// + /// The frame base quantizer index. + /// The distributions indexed by transform-size, plane, and base-range contexts. public static Av1Distribution[][][] GetCoefficientsBaseRange(int baseQIndex) => CoefficientsBaseRange[GetQContext(baseQIndex)]; + /// + /// Gets the coefficient base-level distributions for a base quantizer. + /// + /// The frame base quantizer index. + /// The distributions indexed by transform-size, plane, and nonzero-map contexts. public static Av1Distribution[][][] GetCoefficientsBase(int baseQIndex) => CoefficientsBase[GetQContext(baseQIndex)]; + /// + /// Gets the final-nonzero coefficient base-level distributions for a base quantizer. + /// + /// The frame base quantizer index. + /// The distributions indexed by transform-size, plane, and end-of-block contexts. public static Av1Distribution[][][] GetBaseEndOfBlock(int baseQIndex) => BaseEndOfBlock[GetQContext(baseQIndex)]; + /// + /// Gets the DC sign distributions for a base quantizer. + /// + /// The frame base quantizer index. + /// The distributions indexed by plane and neighboring sign context. public static Av1Distribution[][] GetDcSign(int baseQIndex) => DcSign[GetQContext(baseQIndex)]; + /// + /// Gets the transform-block skip distributions for a base quantizer. + /// + /// The frame base quantizer index. + /// The distributions indexed by transform-size and neighboring skip context. public static Av1Distribution[][] GetTransformBlockSkip(int baseQIndex) => TransformBlockSkip[GetQContext(baseQIndex)]; + /// + /// Gets the end-of-block extra-bit distributions for a base quantizer. + /// + /// The frame base quantizer index. + /// The distributions indexed by transform-size, plane, and end-of-block token context. public static Av1Distribution[][][] GetEndOfBlockExtra(int baseQIndex) => EndOfBlockExtra[GetQContext(baseQIndex)]; + /// + /// Maps a base quantizer index to one of the four AV1 coefficient-probability initialization bands. + /// + /// The base quantizer index. + /// The zero-based quantizer context. private static int GetQContext(int q) { if (q <= 20) diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs index 9c1562592..0a56ec06d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs @@ -6,13 +6,29 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; +/// +/// Derives the AV1 nonzero-coefficient probability context from neighboring coefficient levels and transform geometry. +/// internal static class Av1NzMap { - // SIG_COEF_CONTEXTS_2D = 26 + /// + /// The first one-dimensional nonzero-map context, immediately after the 26 two-dimensional contexts. + /// private const int NzMapContext0 = 26; + + /// + /// The second one-dimensional position band. + /// private const int NzMapContext5 = NzMapContext0 + 5; + + /// + /// The final one-dimensional position band. + /// private const int NzMapContext10 = NzMapContext0 + 10; + /// + /// Maps a horizontal or vertical coefficient coordinate to its one-dimensional context offset. + /// private static readonly int[] NzMapContextOffset1d = [ NzMapContext0, NzMapContext5, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, @@ -21,14 +37,18 @@ internal static class Av1NzMap NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, ]; - // The ctx offset table when TX is TX_CLASS_2D. - // TX col and row indices are clamped to 4 + /// + /// The row-major positional context offsets for a 4x4 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset4x4 = [ 0, 1, 6, 6, 1, 6, 6, 21, 6, 6, 21, 21, 6, 21, 21, 21]; + /// + /// The row-major positional context offsets for an 8x8 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset8x8 = [ 0, 1, 6, 6, 21, 21, 21, 21, 1, 6, 6, 21, 21, 21, 21, 21, @@ -40,6 +60,9 @@ internal static class Av1NzMap 21, 21, 21, 21, 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for a 16x16 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset16x16 = [ 0, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, @@ -59,6 +82,9 @@ internal static class Av1NzMap 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for a 32x32 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset32x32 = [ 0, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, @@ -94,6 +120,9 @@ internal static class Av1NzMap 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for an 8x4 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset8x4 = [ 0, 16, 6, 6, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, @@ -101,6 +130,9 @@ internal static class Av1NzMap 16, 16, 21, 21, 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for a 16x8 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset16x8 = [ 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, @@ -112,6 +144,9 @@ internal static class Av1NzMap 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for a 16x32 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset16x32 = [ 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, @@ -147,6 +182,9 @@ internal static class Av1NzMap 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for a 32x16 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset32x16 = [ 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, @@ -166,6 +204,9 @@ internal static class Av1NzMap 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for the coded low-frequency region of a 32x64 transform. + /// private static readonly int[] NzMapContextOffset32x64 = [ 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, @@ -201,6 +242,9 @@ internal static class Av1NzMap 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for the coded low-frequency region of a 64x32 transform. + /// private static readonly int[] NzMapContextOffset64x32 = [ 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, @@ -236,6 +280,9 @@ internal static class Av1NzMap 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for a 4x16 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset4x16 = [ 0, 11, 11, 11, 11, 11, 11, 11, @@ -255,6 +302,9 @@ internal static class Av1NzMap 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for a 16x4 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset16x4 = [ 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, @@ -262,6 +312,9 @@ internal static class Av1NzMap 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for an 8x32 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset8x32 = [ 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, @@ -297,6 +350,9 @@ internal static class Av1NzMap 21, 21, 21, 21, 21, 21, 21, 21, ]; + /// + /// The row-major positional context offsets for a 32x8 two-dimensional transform. + /// private static readonly int[] NzMapContextOffset32x8 = [ 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, @@ -308,12 +364,18 @@ internal static class Av1NzMap 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, ]; + /// + /// Maps each AV1 transform-size value to its row-major two-dimensional positional context table. + /// private static readonly int[][] NzMapContextOffset = [ + + // Several rectangular transforms reuse a prefix of the next larger table. Every reused prefix has the same + // row-major stride as the adjusted coefficient plane, which avoids duplicating identical context offsets. NzMapContextOffset4x4, // TX_4x4 NzMapContextOffset8x8, // TX_8x8 NzMapContextOffset16x16, // TX_16x16 NzMapContextOffset32x32, // TX_32x32 - NzMapContextOffset32x32, // TX_32x32 + NzMapContextOffset32x32, // TX_64x64 NzMapContextOffset4x16, // TX_4x8 NzMapContextOffset8x4, // TX_8x4 NzMapContextOffset8x32, // TX_8x16 @@ -331,8 +393,12 @@ internal static class Av1NzMap ]; /// - /// SVT: get_nz_mag + /// Sums the clipped magnitudes of the transform-class-specific forward coefficient neighbors. /// + /// The padded absolute-coefficient level plane. + /// The coefficient position. + /// The transform direction class selecting the neighbor pattern. + /// The summed neighbor magnitude used to select a nonzero-map context. public static int GetNzMagnitude(Av1LevelBuffer levels, Point position, Av1TransformClass transformClass) { int mag; @@ -340,7 +406,7 @@ internal static class Av1NzMap Span row1 = levels.GetRow(position.Y + 1)[position.X..]; Span row2 = levels.GetRow(position.Y + 2)[position.X..]; - // Note: AOMMIN(level, 3) is useless for decoder since level < 3. + // Large levels must not dominate probability selection; AV1 contributes at most three from each neighbor. mag = ClipMax3(row0[1]); // { 0, 1 } mag += ClipMax3(row1[0]); // { 1, 0 } @@ -369,30 +435,29 @@ internal static class Av1NzMap return mag; } + /// + /// Combines a neighboring-level statistic with the coefficient's transform-class-specific position band. + /// + /// The clipped sum of the applicable forward-neighbor magnitudes. + /// The coefficient position. + /// The coded transform size selecting the positional table. + /// The transform direction class. + /// The nonzero-map probability context. public static int GetNzMapContextFromStats(int stats, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass) { - // tx_class == 0(TX_CLASS_2D) - if (transformClass == 0 && (position.X == 0) && (position.Y == 0)) + // The DC coefficient has a dedicated 2D context independent of neighboring levels. + if (transformClass == Av1TransformClass.Class2D && position.X == 0 && position.Y == 0) { return 0; } + // Rounding the neighbor sum before clipping produces the five AV1 magnitude bands 0 through 4. int ctx = (stats + 1) >> 1; ctx = Math.Min(ctx, 4); switch (transformClass) { case Av1TransformClass.Class2D: - // This is the algorithm to generate eb_av1_nz_map_ctx_offset[][] - // const int width = tx_size_wide[tx_size]; - // const int height = tx_size_high[tx_size]; - // if (width < height) { - // if (row < 2) return 11 + ctx; - // } else if (width > height) { - // if (col < 2) return 16 + ctx; - // } - // if (row + col < 2) return ctx + 1; - // if (row + col < 4) return 5 + ctx + 1; - // return 21 + ctx; + // The tables preserve AV1's distinct early-row and early-column bands for rectangular transforms. return ctx + GetNzMapContext(transformSize, position); case Av1TransformClass.ClassHorizontal: return ctx + NzMapContextOffset1d[position.X]; @@ -405,9 +470,32 @@ internal static class Av1NzMap return 0; } - public static int GetNzMapContext(Av1TransformSize transformSize, Point pos) => GetNzMapContext(transformSize, pos.X + (pos.Y * transformSize.GetWidth())); + /// + /// Gets the two-dimensional positional context offset for a coefficient position. + /// + /// The coded transform size. + /// The coefficient position. + /// The positional context offset. + public static int GetNzMapContext(Av1TransformSize transformSize, Point position) + { + // AV1 codes only the low-frequency 32-sample region of a 64-point transform dimension. The table still + // uses the signaled shape to select tall or wide bands, but its row-major stride follows the coded region. + int codedWidth = transformSize.GetAdjusted().GetWidth(); + return GetNzMapContext(transformSize, position.X + (position.Y * codedWidth)); + } - public static int GetNzMapContext(Av1TransformSize transformSize, int pos) => NzMapContextOffset[(int)transformSize][pos]; + /// + /// Gets the two-dimensional positional context offset for a row-major coefficient index. + /// + /// The coded transform size. + /// The row-major coefficient index. + /// The positional context offset. + public static int GetNzMapContext(Av1TransformSize transformSize, int position) => NzMapContextOffset[(int)transformSize][position]; + /// + /// Clips a coefficient magnitude to the maximum contribution allowed per neighbor. + /// + /// The coefficient magnitude. + /// The magnitude limited to three. private static int ClipMax3(int value) => Math.Min(value, 3); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs index fbf461b1b..d36c60984 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs @@ -111,26 +111,34 @@ internal static class Av1SymbolContextHelper } /// - /// Derives the lower-level context for the final nonzero coefficient. + /// Derives the lower-level context for the final nonzero coefficient from an index expressed as a two-dimensional coordinate. /// /// The padded coefficient-level buffer. - /// The coefficient position in raster order. + /// The coordinate whose row-major index identifies the coefficient's scan position. /// The end-of-block lower-level context. internal static int GetLowerLevelContextEndOfBlock(Av1LevelBuffer levels, Point position) + => GetLowerLevelContextEndOfBlock(levels, position.X + (position.Y * levels.Size.Width)); + + /// + /// Derives the lower-level context for the final nonzero coefficient from its scan-order index. + /// + /// The padded coefficient-level buffer. + /// The zero-based coefficient index in scan order. + /// The end-of-block lower-level context. + internal static int GetLowerLevelContextEndOfBlock(Av1LevelBuffer levels, int scanIndex) { - if (position.X == 0 && position.Y == 0) + if (scanIndex == 0) { return 0; } int total = levels.Size.Height * levels.Size.Width; - int index = position.X + (position.Y * levels.Size.Width); - if (index <= total >> 3) + if (scanIndex <= total >> 3) { return 1; } - if (index <= total >> 2) + if (scanIndex <= total >> 2) { return 2; } @@ -335,26 +343,19 @@ internal static class Av1SymbolContextHelper } /// - /// Derives the nonzero-map context for one coefficient. + /// Derives the nonzero-map context for one coefficient preceding the final nonzero 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, - bool isEndOfBlock, Av1TransformSize transformSize, Av1TransformClass transformClass) { - if (isEndOfBlock) - { - return (sbyte)GetLowerLevelContextEndOfBlock(levels, position); - } - int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass); return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, position, transformSize, transformClass); } @@ -380,7 +381,12 @@ internal static class Av1SymbolContextHelper { int pos = scan[i]; Point position = levels.GetPosition(pos); - coefficientContexts[pos] = GetNzMapContext(levels, position, i == eob - 1, transformSize, transformClass); + + // The final coefficient context is based on its scan position, while all preceding contexts use the + // coefficient's raster position and already-decoded forward neighbors. + coefficientContexts[pos] = i == eob - 1 + ? (sbyte)GetLowerLevelContextEndOfBlock(levels, i) + : GetNzMapContext(levels, position, transformSize, transformClass); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs index caf760b2e..0597b63fd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs @@ -2,43 +2,158 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; +/// +/// Decodes tile syntax elements and transform coefficients from an AV1 entropy-coded bitstream. +/// internal ref struct Av1SymbolDecoder { + /// + /// Maps each intra prediction mode to the reduced neighbor context used by key-frame luma modes. + /// private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; - private static readonly int[] AlphaVContexts = [-1, 0, 3, -1, 1, 4, -1, 2, 5]; + /// + /// The tile-adaptive intra-block-copy distribution. + /// private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + + /// + /// The tile-adaptive partition-type distributions. + /// private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; + + /// + /// The tile-adaptive key-frame luma-mode distributions. + /// private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode; + + /// + /// The tile-adaptive chroma intra-mode distributions. + /// private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode; + + /// + /// The tile-adaptive transform-skip distributions. + /// private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; + + /// + /// The tile-adaptive skip-mode distributions. + /// private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode; + + /// + /// The tile-adaptive absolute loop-filter delta distribution. + /// private readonly Av1Distribution deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute; + + /// + /// The tile-adaptive absolute quantizer delta distribution. + /// private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute; + + /// + /// The tile-adaptive spatial segment-identifier distributions. + /// private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId; + + /// + /// The tile-adaptive directional angle-delta distributions. + /// private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta; + + /// + /// The tile-adaptive filter-intra mode distribution. + /// private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode; + + /// + /// The tile-adaptive filter-intra enable distributions. + /// private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra; + + /// + /// The tile-adaptive transform-size distributions. + /// private readonly Av1Distribution[][] transformSize = Av1DefaultDistributions.TransformSize; + + /// + /// The tile-adaptive end-of-block token distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][][] endOfBlockFlag; + + /// + /// The tile-adaptive coefficient base-level distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][][] coefficientsBase; + + /// + /// The tile-adaptive final-nonzero coefficient distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][][] baseEndOfBlock; + + /// + /// The tile-adaptive DC sign distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][] dcSign; + + /// + /// The tile-adaptive coefficient base-range distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][][] coefficientsBaseRange; + + /// + /// The tile-adaptive transform-block skip distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][] transformBlockSkip; + + /// + /// The tile-adaptive end-of-block extra-bit distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][][] endOfBlockExtra; + + /// + /// The tile-adaptive joint chroma-from-luma sign distribution. + /// private readonly Av1Distribution chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign; + + /// + /// The tile-adaptive chroma-from-luma alpha-magnitude distributions. + /// private readonly Av1Distribution[] chromaFromLumaAlpha = Av1DefaultDistributions.ChromaFromLumaAlpha; + + /// + /// The tile-adaptive intra transform-type distributions. + /// private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform; + + /// + /// The configuration providing temporary coefficient-context memory. + /// private readonly Configuration configuration; + + /// + /// The range decoder over the current tile payload. + /// private Av1SymbolReader reader; + + /// + /// The frame base quantizer used to select coefficient probability models. + /// private readonly int baseQIndex; + /// + /// Initializes a new instance of the struct for one AV1 tile. + /// + /// The configuration providing temporary memory. + /// The entropy-coded tile payload. + /// The frame base quantizer index. public Av1SymbolDecoder(Configuration configuration, Span tileData, int qIndex) { this.configuration = configuration; @@ -53,18 +168,32 @@ internal ref struct Av1SymbolDecoder this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex); } + /// + /// Reads a fixed-width CDEF strength index. + /// + /// The number of bits signaled for the strength index. + /// The decoded CDEF strength index. public int ReadCdfStrength(int bitCount) { ref Av1SymbolReader r = ref this.reader; return r.ReadLiteral(bitCount); } + /// + /// Reads the frame-local intra-block-copy flag. + /// + /// when intra-block copy is selected. public bool ReadUseIntraBlockCopy() { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.tileIntraBlockCopy) > 0; } + /// + /// Reads a complete block partition type from the selected partition context. + /// + /// The partition probability context. + /// The decoded partition type. public Av1PartitionType ReadPartitionType(int context) { ref Av1SymbolReader r = ref this.reader; @@ -72,8 +201,11 @@ internal ref struct Av1SymbolDecoder } /// - /// SVT: partition_gather_vert_alike + /// Reads the binary split-versus-horizontal decision used at a clipped right tile boundary. /// + /// The current block size. + /// The partition probability context. + /// or . public Av1PartitionType ReadSplitOrHorizontal(Av1BlockSize blockSize, int context) { Av1Distribution distribution = GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context); @@ -82,8 +214,11 @@ internal ref struct Av1SymbolDecoder } /// - /// SVT: partition_gather_horz_alike + /// Reads the binary split-versus-vertical decision used at a clipped bottom tile boundary. /// + /// The current block size. + /// The partition probability context. + /// or . public Av1PartitionType ReadSplitOrVertical(Av1BlockSize blockSize, int context) { Av1Distribution distribution = GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, context); @@ -91,6 +226,12 @@ internal ref struct Av1SymbolDecoder return r.ReadSymbol(distribution) > 0 ? Av1PartitionType.Split : Av1PartitionType.Vertical; } + /// + /// Reads a key-frame luma prediction mode using the available above and left modes. + /// + /// The above block mode, or at the frame boundary. + /// The left block mode, or at the frame boundary. + /// The decoded luma prediction mode. public Av1PredictionMode ReadYMode(Av1BlockModeInfo? aboveModeInfo, Av1BlockModeInfo? leftModeInfo) { ref Av1SymbolReader r = ref this.reader; @@ -111,6 +252,12 @@ internal ref struct Av1SymbolDecoder return (Av1PredictionMode)r.ReadSymbol(this.keyFrameYMode[aboveContext][leftContext]); } + /// + /// Reads a chroma intra prediction mode conditioned on the luma mode and chroma-from-luma availability. + /// + /// The decoded luma prediction mode. + /// Indicates whether chroma-from-luma is valid for the block. + /// The decoded chroma prediction mode. public Av1PredictionMode ReadIntraModeUv(Av1PredictionMode mode, bool chromaFromLumaAllowed) { int chromaForLumaIndex = chromaFromLumaAllowed ? 1 : 0; @@ -118,18 +265,32 @@ internal ref struct Av1SymbolDecoder return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)mode]); } + /// + /// Reads the transform-skip flag from a neighboring skip context. + /// + /// The neighboring skip context. + /// when the block contains no coded transform coefficients. public bool ReadSkip(int ctx) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.skip[ctx]) > 0; } - public bool ReadSkipMode(Av1BlockSize blockSize) + /// + /// Reads the compound-reference skip-mode flag. + /// + /// The neighboring skip-mode context. + /// when skip mode is selected. + public bool ReadSkipMode(int context) { ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(this.skipMode[(int)blockSize]) > 0; + return r.ReadSymbol(this.skipMode[context]) > 0; } + /// + /// Reads a signed loop-filter delta value. + /// + /// The decoded loop-filter delta. public int ReadDeltaLoopFilter() { ref Av1SymbolReader r = ref this.reader; @@ -151,8 +312,9 @@ internal ref struct Av1SymbolDecoder } /// - /// SVT: read_delta_qindex + /// Reads a signed quantizer-index delta value. /// + /// The decoded quantizer-index delta. public int ReadDeltaQuantizerIndex() { ref Av1SymbolReader r = ref this.reader; @@ -173,18 +335,33 @@ internal ref struct Av1SymbolDecoder return deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; } + /// + /// Reads a spatially predicted segment identifier. + /// + /// The context derived from neighboring segment identifiers. + /// The decoded segment identifier. public int ReadSegmentId(int context) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.segmentId[context]); } + /// + /// Reads the unsigned directional angle-delta symbol for a prediction mode. + /// + /// The directional prediction mode. + /// The symbol in the range zero through twice the maximum signed angle delta. public int ReadAngleDelta(Av1PredictionMode mode) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.angleDelta[(int)mode - 1]); } + /// + /// Reads the filter-intra enable flag and, when enabled, its prediction mode. + /// + /// The block size selecting the enable distribution. + /// The selected mode, or when filter-intra is disabled. public Av1FilterIntraMode ReadFilterUltraMode(Av1BlockSize blockSize) { ref Av1SymbolReader r = ref this.reader; @@ -198,6 +375,12 @@ internal ref struct Av1SymbolDecoder return filterIntraMode; } + /// + /// Reads a transform subdivision depth and resolves it to a transform size. + /// + /// The block size defining the maximum transform. + /// The neighboring transform-size context. + /// The decoded transform size. public Av1TransformSize ReadTransformSize(Av1BlockSize blockSize, int context) { ref Av1SymbolReader r = ref this.reader; @@ -223,8 +406,15 @@ internal ref struct Av1SymbolDecoder } /// - /// SVT: parse_transform_type + /// Reads an intra transform type from the transform set permitted for the block. /// + /// The coded transform size. + /// Indicates whether the frame restricts transform choices. + /// Indicates whether filter-intra prediction selected the intra direction. + /// The active base quantizer index. + /// The filter-intra mode when enabled. + /// The ordinary intra prediction mode. + /// The decoded transform type, or DCT-DCT when no transform type is signaled. public Av1TransformType ReadTransformType( Av1TransformSize transformSize, bool useReducedTransformSet, @@ -235,20 +425,13 @@ internal ref struct Av1SymbolDecoder { Av1TransformType transformType = Av1TransformType.DctDct; - /* - // No need to read transform type if block is skipped. - if (mbmi.Skip || - svt_aom_seg_feature_active(&parse_ctxt->frame_header->segmentation_params, mbmi->segment_id, SEG_LVL_SKIP)) - return; - */ - + // A zero base quantizer selects DCT-DCT and carries no transform-type symbol in this intra path. if (baseQIndex == 0) { return transformType; } - // Ignoring INTER blocks here, as these should not end up here. - // int inter_block = is_inter_block_dec(mbmi); + // Still-image decoding reaches this path only for intra blocks, so the intra transform set is authoritative. Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet); if (transformSetType > Av1TransformSetType.DctOnly && baseQIndex > 0) { @@ -265,35 +448,73 @@ internal ref struct Av1SymbolDecoder return transformType; } + /// + /// Reads whether a transform block has no coded coefficients. + /// + /// The square transform-size probability context. + /// The context derived from neighboring coefficient blocks. + /// when the transform block is empty. public bool ReadTransformBlockSkip(Av1TransformSize transformSizeContext, int skipContext) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.transformBlockSkip[(int)transformSizeContext][skipContext]) > 0; } + /// + /// Reads the joint U/V sign symbol for chroma-from-luma alpha values. + /// + /// The joint sign symbol. public int ReadChromFromLumaSign() { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.chromaFromLumaSign); } + /// + /// Reads the U-plane chroma-from-luma alpha-magnitude symbol. + /// + /// The one-based joint U/V sign symbol. + /// The U-plane alpha-magnitude symbol. public int ReadChromaFromLumaAlphaU(int jointSignPlus1) { ref Av1SymbolReader r = ref this.reader; - int context = jointSignPlus1 - 3; + int context = Av1ChromaFromLumaMath.ContextU(jointSignPlus1 - 1); return r.ReadSymbol(this.chromaFromLumaAlpha[context]); } + /// + /// Reads the V-plane chroma-from-luma alpha-magnitude symbol. + /// + /// The one-based joint U/V sign symbol. + /// The V-plane alpha-magnitude symbol. public int ReadChromaFromLumaAlphaV(int jointSignPlus1) { ref Av1SymbolReader r = ref this.reader; - int context = AlphaVContexts[jointSignPlus1]; + int context = Av1ChromaFromLumaMath.ContextV(jointSignPlus1 - 1); return r.ReadSymbol(this.chromaFromLumaAlpha[context]); } /// - /// SVT: parse_coeffs + /// Decodes one transform block's coefficient syntax and updates its neighboring entropy contexts. /// + /// The current block prediction and segment modes. + /// The transform-block position in four-sample units. + /// The above coefficient contexts for the current plane. + /// The left coefficient contexts for the current plane. + /// The first above context covered by the transform. + /// The first left context covered by the transform. + /// The zero-based Y, U, or V plane index. + /// The available plane width in four-sample units. + /// The available plane height in four-sample units. + /// The neighboring skip and DC sign contexts. + /// The signaled transform size. + /// Indicates whether the active segment is lossless. + /// Indicates whether the frame restricts transform choices. + /// The transform descriptor updated with the decoded type and coded-block flag. + /// The signed distance from the mode block to the right frame edge. + /// The signed distance from the mode block to the bottom frame edge. + /// The destination receiving the coefficient count followed by scan-ordered signed levels. + /// The one-based end-of-block position, or zero for an empty transform block. public int ReadCoefficients( Av1BlockModeInfo modeInfo, Point blockPosition, @@ -313,13 +534,15 @@ internal ref struct Av1SymbolDecoder int modeBlocksToBottomEdge, Span coefficientBuffer) { - int width = transformSize.GetWidth(); - int height = transformSize.GetHeight(); + Av1TransformSize adjustedTransformSize = transformSize.GetAdjusted(); + int width = adjustedTransformSize.GetWidth(); + int height = adjustedTransformSize.GetHeight(); Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize); Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); int culLevel = 0; - Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); + // AV1 omits high-frequency coefficients beyond 32 samples on every 64-point transform dimension. + using Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); bool allZero = this.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); int endOfBlock; @@ -373,6 +596,14 @@ internal ref struct Av1SymbolDecoder return endOfBlock; } + /// + /// Reads an end-of-block token and its literal suffix. + /// + /// The signaled transform size selecting the token alphabet. + /// The transform class selecting the two-dimensional or one-dimensional model. + /// The square transform-size probability context. + /// The luma or chroma plane category. + /// The one-based end-of-block coefficient position. public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformClass transformClass, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { ref Av1SymbolReader r = ref this.reader; @@ -381,7 +612,8 @@ internal ref struct Av1SymbolDecoder int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint]; if (endOfBlockShift > 0) { - int endOfBlockContext = endOfBlockPoint; + // Extra-bit distributions start with token three because the first three tokens have no extra bits. + int endOfBlockContext = endOfBlockPoint - 3; bool bit = this.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext); if (bit) { @@ -400,13 +632,21 @@ internal ref struct Av1SymbolDecoder return Av1SymbolContextHelper.RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); } + /// + /// Decodes the mandatory nonzero coefficient at the end-of-block scan position. + /// + /// The transform direction class. + /// The one-based end-of-block position. + /// The transform's scan-to-raster mapping. + /// The padded absolute-coefficient level plane to update. + /// The square transform-size probability context. + /// The luma or chroma plane category. public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, ReadOnlySpan scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { int i = endOfBlock - 1; Point position = levels.GetPosition(scan[i]); - int coefficientContext = Av1SymbolContextHelper.GetLowerLevelContextEndOfBlock(levels, position); + int coefficientContext = Av1SymbolContextHelper.GetLowerLevelContextEndOfBlock(levels, i); int level = this.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext) + 1; - Av1TransformSize limitedTransformSizeContext = (Av1TransformSize)Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32); if (level > Av1Constants.BaseLevelsCount) { int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContextEndOfBlock(position, transformClass); @@ -416,9 +656,18 @@ internal ref struct Av1SymbolDecoder levels.GetRow(position)[position.X] = (byte)level; } + /// + /// Decodes a reverse scan range using the specialized two-dimensional coefficient contexts. + /// + /// The signaled transform size. + /// The inclusive lowest scan index. + /// The inclusive highest scan index. + /// The transform's scan-to-raster mapping. + /// The padded absolute-coefficient level plane to update. + /// The square transform-size probability context. + /// The luma or chroma plane category. public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { - Av1TransformSize limitedTransformSizeContext = (Av1TransformSize)Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32); for (int c = endScanIndex; c >= startScanIndex; --c) { Point position = levels.GetPosition(scan[c]); @@ -434,9 +683,19 @@ internal ref struct Av1SymbolDecoder } } + /// + /// Decodes a reverse scan range using transform-class-specific coefficient contexts. + /// + /// The signaled transform size. + /// The transform direction class. + /// The inclusive lowest scan index. + /// The inclusive highest scan index. + /// The transform's scan-to-raster mapping. + /// The padded absolute-coefficient level plane to update. + /// The square transform-size probability context. + /// The luma or chroma plane category. public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { - Av1TransformSize limitedTransformSizeContext = (Av1TransformSize)Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32); for (int c = endScanIndex; c >= startScanIndex; --c) { int pos = scan[c]; @@ -453,21 +712,30 @@ internal ref struct Av1SymbolDecoder } } + /// + /// Reads coefficient signs and Golomb extensions, then writes scan-ordered signed levels. + /// + /// The destination receiving the coefficient count followed by signed levels. + /// The one-based end-of-block position and coefficient count. + /// The transform's scan-to-raster mapping. + /// The decoded absolute-coefficient level plane. + /// The neighboring DC sign context. + /// The luma or chroma plane category. + /// The packed coefficient context used by adjacent transform blocks. public int ReadCoefficientsSign(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType) { ref Av1SymbolReader r = ref this.reader; - int maxScanLine = 0; int culLevel = 0; int dcValue = 0; coefficientBuffer[0] = endOfBlock; for (int c = 0; c < endOfBlock; c++) { int sign = 0; - Point position = levels.GetPosition(c); + int pos = scan[c]; + Point position = levels.GetPosition(pos); int level = levels[position]; if (level != 0) { - maxScanLine = Math.Max(maxScanLine, scan[c]); if (c == 0) { sign = this.ReadDcSign(planeType, dcSignContext); @@ -500,6 +768,13 @@ internal ref struct Av1SymbolDecoder return culLevel; } + /// + /// Reads the end-of-block token for a transform coefficient-count category. + /// + /// The luma or chroma plane category. + /// The transform direction class. + /// The signaled transform size. + /// The one-based end-of-block token. private int ReadEndOfBlockFlag(Av1PlaneType planeType, Av1TransformClass transformClass, Av1TransformSize transformSize) { int endOfBlockContext = transformClass == Av1TransformClass.Class2D ? 0 : 1; @@ -508,36 +783,77 @@ internal ref struct Av1SymbolDecoder return r.ReadSymbol(this.endOfBlockFlag[endOfBlockMultiSize][(int)planeType][endOfBlockContext]) + 1; } + /// + /// Reads the most significant context-coded bit of an end-of-block suffix. + /// + /// The square transform-size probability context. + /// The luma or chroma plane category. + /// The zero-based extra-bit token context. + /// The decoded suffix bit. private bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.endOfBlockExtra[(int)transformSizeContext][(int)planeType][endOfBlockContext]) > 0; } + /// + /// Reads one coefficient base-range symbol. + /// + /// The square transform-size probability context. + /// The luma or chroma plane category. + /// The coefficient base-range context. + /// The decoded base-range symbol. private int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.coefficientsBaseRange[(int)transformSizeContext][(int)planeType][baseRangeContext]); } + /// + /// Reads the sign of a nonzero DC coefficient. + /// + /// The luma or chroma plane category. + /// The neighboring DC sign context. + /// Zero for positive or one for negative. private int ReadDcSign(Av1PlaneType planeType, int dcSignContext) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.dcSign[(int)planeType][dcSignContext]); } + /// + /// Reads the base-level symbol for the final nonzero coefficient. + /// + /// The square transform-size probability context. + /// The luma or chroma plane category. + /// The end-of-block coefficient context. + /// The zero-based base-level symbol. private int ReadBaseEndOfBlock(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.baseEndOfBlock[(int)transformSizeContext][(int)planeType][coefficientContext]); } + /// + /// Reads the base-level symbol for a coefficient preceding end-of-block. + /// + /// The square transform-size probability context. + /// The luma or chroma plane category. + /// The nonzero-map coefficient context. + /// The decoded base-level symbol. private int ReadCoefficientsBase(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]); } + /// + /// Accumulates coefficient base-range symbols until the terminal symbol or AV1 range limit is reached. + /// + /// The square transform-size probability context. + /// The luma or chroma plane category. + /// The coefficient base-range context. + /// The coefficient level to increment. private void ReadCoefficientsBaseRangeLoop(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext, ref int level) { ref Av1SymbolReader r = ref this.reader; @@ -554,6 +870,11 @@ internal ref struct Av1SymbolDecoder } } + /// + /// Reads the unsigned exponential-Golomb suffix used for coefficient levels beyond the base range. + /// + /// The decoded nonnegative suffix value. + /// The unary prefix exceeds the AV1 coefficient limit. internal int ReadGolomb() { ref Av1SymbolReader r = ref this.reader; @@ -567,8 +888,7 @@ internal ref struct Av1SymbolDecoder ++length; if (length > 20) { - // SVT_LOG("Invalid length in read_golomb"); - break; + throw new InvalidImageContentException("The AV1 coefficient Golomb code exceeds its 20-bit limit."); } } @@ -581,6 +901,21 @@ internal ref struct Av1SymbolDecoder return x - 1; } + /// + /// Stores a transform block's packed coefficient context into the above and left neighbor arrays. + /// + /// The current block mode information. + /// The above contexts for the current plane. + /// The left contexts for the current plane. + /// The available plane width in four-sample units. + /// The available plane height in four-sample units. + /// The signaled transform size. + /// The transform-block position in four-sample units. + /// The first above context covered by the transform. + /// The first left context covered by the transform. + /// The packed coefficient magnitude and DC sign context. + /// The signed distance from the mode block to the right frame edge. + /// The signed distance from the mode block to the bottom frame edge. private static void UpdateCoefficientContext( Av1BlockModeInfo modeInfo, int[] aboveContexts, @@ -613,7 +948,7 @@ internal ref struct Av1SymbolDecoder { int leftContextCount = Math.Min(transformSizeHigh, blocksHigh - leftOffset); Array.Fill(leftContexts, culLevel, 0, leftContextCount); - Array.Fill(leftContexts, 0, leftContextCount, transformSizeWide - leftContextCount); + Array.Fill(leftContexts, 0, leftContextCount, transformSizeHigh - leftContextCount); } else { @@ -621,6 +956,16 @@ internal ref struct Av1SymbolDecoder } } + /// + /// Resolves the transform type permitted for a plane after lossless, size, prediction, and transform-set restrictions. + /// + /// The luma or chroma plane category. + /// The current block prediction modes. + /// Indicates whether the active segment is lossless. + /// The signaled transform size. + /// The transform descriptor containing the signaled luma type. + /// Indicates whether the frame restricts transform choices. + /// The transform type valid for the current plane. private static Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1BlockModeInfo modeInfo, bool isLossless, Av1TransformSize transformSize, Av1TransformInfo transformInfo, bool useReducedTransformSet) { Av1TransformType transformType = Av1TransformType.DctDct; @@ -636,8 +981,7 @@ internal ref struct Av1SymbolDecoder } else { - // In intra mode, uv planes don't share the same prediction mode as y - // plane, so the tx_type should not be shared + // Chroma has its own intra mode, so its implicit transform must be derived independently of luma. transformType = Av1SymbolContextHelper.ConvertIntraModeToTransformType(modeInfo, Av1PlaneType.Uv); } } @@ -651,9 +995,18 @@ internal ref struct Av1SymbolDecoder return transformType; } + /// + /// Collapses a full partition distribution into the split-versus-horizontal boundary decision. + /// + /// The full partition distributions. + /// The current block size. + /// The partition probability context. + /// The binary cumulative distribution for split versus the horizontal-like partition group. internal static Av1Distribution GetSplitOrHorizontalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) { Av1Distribution input = inputs[context]; + + // At a clipped right edge, all syntax choices that advance horizontally collapse into one binary outcome. uint p = Av1Distribution.ProbabilityTop; p -= GetElementProbability(input, Av1PartitionType.Horizontal); p -= GetElementProbability(input, Av1PartitionType.Split); @@ -668,9 +1021,18 @@ internal ref struct Av1SymbolDecoder return new(Av1Distribution.ProbabilityTop - p); } + /// + /// Collapses a full partition distribution into the split-versus-vertical boundary decision. + /// + /// The full partition distributions. + /// The current block size. + /// The partition probability context. + /// The binary cumulative distribution for split versus the vertical-like partition group. internal static Av1Distribution GetSplitOrVerticalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) { Av1Distribution input = inputs[context]; + + // At a clipped bottom edge, all syntax choices that advance vertically collapse into one binary outcome. uint p = Av1Distribution.ProbabilityTop; p -= GetElementProbability(input, Av1PartitionType.Vertical); p -= GetElementProbability(input, Av1PartitionType.Split); @@ -685,6 +1047,12 @@ internal ref struct Av1SymbolDecoder return new(Av1Distribution.ProbabilityTop - p); } + /// + /// Gets one symbol's probability mass from adjacent inverse-CDF thresholds. + /// + /// The inverse cumulative distribution. + /// The partition symbol. + /// The symbol's probability mass. private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) => probability[(int)element - 1] - probability[(int)element]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index 4f98f446f..8090c7045 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -4,39 +4,148 @@ using System.Buffers; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; +/// +/// Encodes AV1 tile syntax elements and transform coefficients with tile-local adaptive distributions. +/// internal class Av1SymbolEncoder : IDisposable { + /// + /// The tile-adaptive intra-block-copy distribution. + /// private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + + /// + /// The tile-adaptive partition-type distributions. + /// private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; + + /// + /// The tile-adaptive key-frame luma-mode distributions. + /// private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode; + + /// + /// The tile-adaptive chroma intra-mode distributions. + /// private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode; + + /// + /// The tile-adaptive transform-block skip distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][] transformBlockSkip; + + /// + /// The tile-adaptive end-of-block token distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][][] endOfBlockFlag; + + /// + /// The tile-adaptive coefficient base-range distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][][] coefficientsBaseRange; + + /// + /// The tile-adaptive coefficient base-level distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][][] coefficientsBase; + + /// + /// The tile-adaptive final-nonzero coefficient distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][][] coefficientsBaseEndOfBlock; + + /// + /// The tile-adaptive filter-intra enable distributions. + /// private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra; + + /// + /// The tile-adaptive filter-intra mode distribution. + /// private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode; + + /// + /// The tile-adaptive absolute quantizer delta distribution. + /// private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute; + + /// + /// The tile-adaptive DC sign distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][] dcSign; + + /// + /// The tile-adaptive end-of-block extra-bit distributions selected for the frame base quantizer. + /// private readonly Av1Distribution[][][] endOfBlockExtra; + + /// + /// The tile-adaptive intra transform-type distributions. + /// private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform; + + /// + /// The tile-adaptive spatial segment-identifier distributions. + /// private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId; + + /// + /// The tile-adaptive directional angle-delta distributions. + /// private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta; + + /// + /// The tile-adaptive transform-skip distributions. + /// private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; + + /// + /// The tile-adaptive skip-mode distributions. + /// private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode; + + /// + /// The tile-adaptive joint chroma-from-luma sign distribution. + /// private readonly Av1Distribution chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign; + + /// + /// The tile-adaptive chroma-from-luma alpha-magnitude distributions. + /// private readonly Av1Distribution[] chromaFromLumaAlpha = Av1DefaultDistributions.ChromaFromLumaAlpha; + + /// + /// Indicates whether the range writer has been disposed. + /// private bool isDisposed; + + /// + /// The configuration providing output and coefficient-context memory. + /// private readonly Configuration configuration; + + /// + /// The range writer producing the current tile payload. + /// private Av1SymbolWriter writer; + + /// + /// The frame base quantizer used to select coefficient probability models. + /// private readonly int baseQIndex; + /// + /// Initializes a new instance of the class for one AV1 tile. + /// + /// The configuration providing output and temporary memory. + /// The initial output buffer size in bytes. + /// The frame base quantizer index. public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex) { this.transformBlockSkip = Av1DefaultDistributions.GetTransformBlockSkip(qIndex); @@ -51,18 +160,33 @@ internal class Av1SymbolEncoder : IDisposable this.baseQIndex = qIndex; } + /// + /// Writes the frame-local intra-block-copy flag. + /// + /// Indicates whether intra-block copy is selected. public void WriteUseIntraBlockCopy(bool value) { ref Av1SymbolWriter w = ref this.writer; w.WriteSymbol(value, this.tileIntraBlockCopy); } + /// + /// Writes a complete block partition type using the selected partition context. + /// + /// The partition type to encode. + /// The partition probability context. public void WritePartitionType(Av1PartitionType partitionType, int context) { ref Av1SymbolWriter w = ref this.writer; w.WriteSymbol((int)partitionType, this.tilePartitionTypes[context]); } + /// + /// Writes the split-versus-horizontal boundary decision for a block clipped at the right tile edge. + /// + /// The split or horizontal partition outcome. + /// The current block size. + /// The partition probability context. public void WriteSplitOrHorizontal(Av1PartitionType partitionType, Av1BlockSize blockSize, int context) { Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context); @@ -71,6 +195,12 @@ internal class Av1SymbolEncoder : IDisposable w.WriteSymbol(value, distribution); } + /// + /// Writes the split-versus-vertical boundary decision for a block clipped at the bottom tile edge. + /// + /// The split or vertical partition outcome. + /// The current block size. + /// The partition probability context. public void WriteSplitOrVertical(Av1PartitionType partitionType, Av1BlockSize blockSize, int context) { Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, context); @@ -80,8 +210,18 @@ internal class Av1SymbolEncoder : IDisposable } /// - /// SVT: av1_write_coeffs_txb_1d + /// Encodes one transform block's coefficient syntax using scan-order probability contexts. /// + /// The signaled transform size. + /// The transform type selecting the scan and context class. + /// The block's intra prediction mode. + /// The raster-ordered signed coefficient levels. + /// The luma or chroma component category. + /// The neighboring skip and DC sign contexts. + /// The one-based final nonzero scan position, or zero for an empty block. + /// Indicates whether the frame restricts transform choices. + /// The selected filter-intra mode, or the disabled sentinel. + /// The packed coefficient context used by adjacent transform blocks. public int WriteCoefficients( Av1TransformSize transformSize, Av1TransformType transformType, @@ -94,17 +234,18 @@ internal class Av1SymbolEncoder : IDisposable Av1FilterIntraMode filterIntraMode) { int c; - int width = transformSize.GetWidth(); - int height = transformSize.GetHeight(); + Av1TransformSize adjustedTransformSize = transformSize.GetAdjusted(); + int width = adjustedTransformSize.GetWidth(); + int height = adjustedTransformSize.GetHeight(); Av1TransformClass transformClass = transformType.ToClass(); Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); ReadOnlySpan scan = scanOrder.Scan; - int blockWidthLog2 = transformSize.GetBlockWidthLog2(); Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize); ref Av1SymbolWriter w = ref this.writer; - Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); + // AV1 omits high-frequency coefficients beyond 32 samples on every 64-point transform dimension. + using Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); Span coefficientContexts = new sbyte[width * height]; Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext)); @@ -145,7 +286,7 @@ internal class Av1SymbolEncoder : IDisposable if (level > Av1Constants.BaseLevelsCount) { - // level is above 1. + // Base-range symbols extend levels above the two base levels in fixed-size chunks. int baseRange = level - 1 - Av1Constants.BaseLevelsCount; int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, position, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) @@ -160,15 +301,14 @@ internal class Av1SymbolEncoder : IDisposable } } - // Loop to code all signs in the transform block, - // starting with the sign of DC (if applicable) - int cul_level = 0; + // Signs follow every magnitude so the DC sign can use its neighboring context and AC signs remain literals. + int culLevel = 0; for (c = 0; c < endOfBlock; ++c) { short pos = scan[c]; int v = coefficientBuffer[pos]; int level = Math.Abs(v); - cul_level += level; + culLevel += level; uint sign = v < 0 ? 1u : 0u; if (level > 0) @@ -189,13 +329,21 @@ internal class Av1SymbolEncoder : IDisposable } } - cul_level = Math.Min(Av1Constants.CoefficientContextMask, cul_level); + culLevel = Math.Min(Av1Constants.CoefficientContextMask, culLevel); - // DC value - Av1SymbolContextHelper.SetDcSign(ref cul_level, coefficientBuffer[0]); - return cul_level; + // The DC sign is packed above the magnitude bits so adjacent blocks can derive both contexts from one value. + Av1SymbolContextHelper.SetDcSign(ref culLevel, coefficientBuffer[0]); + return culLevel; } + /// + /// Writes an end-of-block token and its context-coded and literal suffix bits. + /// + /// The one-based final nonzero scan position. + /// The luma or chroma component category. + /// The transform direction class. + /// The signaled transform size selecting the token alphabet. + /// The square transform-size probability context. internal void WriteEndOfBlockPosition(ushort endOfBlock, Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, Av1TransformSize transformSizeContext) { short endOfBlockPosition = Av1SymbolContextHelper.GetEndOfBlockPosition(endOfBlock, out int eobExtra); @@ -207,7 +355,8 @@ internal class Av1SymbolEncoder : IDisposable ref Av1SymbolWriter w = ref this.writer; int eobShift = eobOffsetBitCount - 1; int bit = Av1Math.GetBit(eobExtra, eobShift); - w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockPosition]); + int endOfBlockContext = endOfBlockPosition - 3; + w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockContext]); for (int i = 1; i < eobOffsetBitCount; i++) { eobShift = eobOffsetBitCount - 1 - i; @@ -217,18 +366,31 @@ internal class Av1SymbolEncoder : IDisposable } } + /// + /// Writes whether a transform block has no coded coefficients. + /// + /// Indicates whether the transform block is empty. + /// The square transform-size probability context. + /// The context derived from neighboring coefficient blocks. internal void WriteTransformBlockSkip(bool skip, Av1TransformSize transformSizeContext, int skipContext) { ref Av1SymbolWriter w = ref this.writer; w.WriteSymbol(skip, this.transformBlockSkip[(int)transformSizeContext][skipContext]); } + /// + /// Finalizes the range-coded tile payload and transfers ownership of its memory. + /// + /// The memory owner containing the encoded tile bytes. public IMemoryOwner Exit() { ref Av1SymbolWriter w = ref this.writer; return w.Exit(); } + /// + /// Releases output memory that has not been transferred by . + /// public void Dispose() { if (!this.isDisposed) @@ -239,8 +401,9 @@ internal class Av1SymbolEncoder : IDisposable } /// - /// SVT: write_golomb + /// Writes the unsigned exponential-Golomb suffix used for coefficient levels beyond the base range. /// + /// The nonnegative suffix value. internal void WriteGolomb(int level) { uint x = (uint)level + 1u; @@ -260,6 +423,13 @@ internal class Av1SymbolEncoder : IDisposable } } + /// + /// Writes the end-of-block token for a transform coefficient-count category. + /// + /// The luma or chroma component category. + /// The transform direction class. + /// The signaled transform size. + /// The one-based end-of-block token. private void WriteEndOfBlockFlag(Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, int endOfBlockPosition) { int endOfBlockMultiSize = transformSize.GetLog2Minus4(); @@ -269,8 +439,14 @@ internal class Av1SymbolEncoder : IDisposable } /// - /// SVT: av1_write_tx_type + /// Writes an intra transform type when the permitted transform set contains multiple choices. /// + /// The transform type to encode. + /// The signaled transform size. + /// Indicates whether the frame restricts transform choices. + /// The active base quantizer index. + /// The filter-intra mode when enabled. + /// The ordinary intra prediction mode. internal void WriteTransformType( Av1TransformType transformType, Av1TransformSize transformSize, @@ -279,7 +455,7 @@ internal class Av1SymbolEncoder : IDisposable Av1FilterIntraMode filterIntraMode, Av1PredictionMode intraDirection) { - // bool isInter = mbmi->block_mi.use_intrabc || is_inter_mode(mbmi->block_mi.mode); + // Still-image encoding reaches this path only for intra blocks, so the intra transform set is authoritative. Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet); if (Av1SymbolContextHelper.GetExtendedTransformTypeCount(transformSetType) > 1 && baseQIndex > 0) { @@ -288,11 +464,9 @@ internal class Av1SymbolEncoder : IDisposable int extendedSet = Av1SymbolContextHelper.GetExtendedTransformSet(transformSetType); - // eset == 0 should correspond to a set with only DCT_DCT and there - // is no need to send the tx_type + // Set zero contains only DCT-DCT, which was excluded by the multiple-choice condition above. Guard.MustBeGreaterThan(extendedSet, 0, nameof(extendedSet)); - // assert(av1_ext_tx_used[tx_set_type][transformType]); Av1PredictionMode intraDirectionContext; if (filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes) { @@ -312,24 +486,44 @@ internal class Av1SymbolEncoder : IDisposable } } + /// + /// Writes a spatially predicted segment identifier. + /// + /// The segment identifier. + /// The context derived from neighboring segment identifiers. internal void WriteSegmentId(int segmentId, int context) { ref Av1SymbolWriter w = ref this.writer; w.WriteSymbol(segmentId, this.segmentId[context]); } + /// + /// Writes the transform-skip flag from a neighboring skip context. + /// + /// Indicates whether the block contains no coded transform coefficients. + /// The neighboring skip context. internal void WriteSkip(bool skip, int context) { ref Av1SymbolWriter w = ref this.writer; w.WriteSymbol(skip, this.skip[context]); } + /// + /// Writes the compound-reference skip-mode flag. + /// + /// Indicates whether skip mode is selected. + /// The neighboring skip-mode context. internal void WriteSkipMode(bool skip, int context) { ref Av1SymbolWriter w = ref this.writer; w.WriteSymbol(skip, this.skipMode[context]); } + /// + /// Writes the filter-intra enable flag and, when enabled, its prediction mode. + /// + /// The selected filter-intra mode, or the disabled sentinel. + /// The block size selecting the enable distribution. internal void WriteFilterIntraMode(Av1FilterIntraMode filterIntraMode, Av1BlockSize blockSize) { ref Av1SymbolWriter w = ref this.writer; @@ -342,23 +536,25 @@ internal class Av1SymbolEncoder : IDisposable } /// - /// SVT: av1_write_delta_q_index + /// Writes a signed quantizer-index delta value. /// + /// The signed quantizer-index delta. internal void WriteDeltaQuantizerIndex(int deltaQindex) { ref Av1SymbolWriter w = ref this.writer; bool sign = deltaQindex < 0; int abs = Math.Abs(deltaQindex); - bool smallval = abs < Av1Constants.DeltaQuantizerSmall; + bool isSmallValue = abs < Av1Constants.DeltaQuantizerSmall; w.WriteSymbol(Math.Min(abs, Av1Constants.DeltaQuantizerSmall), this.deltaQuantizerAbsolute); - if (!smallval) + if (!isSmallValue) { - int rem_bits = Av1Math.MostSignificantBit((uint)(abs - 1)); - int threshold = (1 << rem_bits) + 1; - w.WriteLiteral((uint)(rem_bits - 1), 3); - w.WriteLiteral((uint)(abs - threshold), rem_bits); + // Escape magnitudes encode their bit width first, followed by the offset within that width's range. + int remainingBitCount = Av1Math.MostSignificantBit((uint)(abs - 1)); + int threshold = (1 << remainingBitCount) + 1; + w.WriteLiteral((uint)(remainingBitCount - 1), 3); + w.WriteLiteral((uint)(abs - threshold), remainingBitCount); } if (abs > 0) @@ -367,24 +563,46 @@ internal class Av1SymbolEncoder : IDisposable } } + /// + /// Writes a key-frame luma prediction mode using the above and left mode contexts. + /// + /// The luma prediction mode. + /// The reduced above-mode context. + /// The reduced left-mode context. internal void WriteLumaMode(Av1PredictionMode lumaMode, byte topContext, byte leftContext) { ref Av1SymbolWriter w = ref this.writer; w.WriteSymbol((int)lumaMode, this.keyFrameYMode[topContext][leftContext]); } + /// + /// Writes an unsigned directional angle-delta symbol. + /// + /// The signed angle delta offset by . + /// The directional prediction mode selecting the distribution. internal void WriteAngleDelta(int angleDelta, Av1PredictionMode context) { ref Av1SymbolWriter w = ref this.writer; w.WriteSymbol(angleDelta, this.angleDelta[context - Av1PredictionMode.Vertical]); } + /// + /// Writes a fixed-width CDEF strength index. + /// + /// The CDEF strength index. + /// The number of signaled bits. internal void WriteCdefStrength(int cdefStrength, int bitCount) { ref Av1SymbolWriter w = ref this.writer; w.WriteLiteral((uint)cdefStrength, bitCount); } + /// + /// Writes a chroma intra prediction mode conditioned on the luma mode and chroma-from-luma availability. + /// + /// The chroma prediction mode. + /// Indicates whether chroma-from-luma is valid for the block. + /// The block's luma prediction mode. internal void WriteChromaMode(Av1PredictionMode chromaMode, bool isChromaFromLumaAllowed, Av1PredictionMode lumaMode) { ref Av1SymbolWriter w = ref this.writer; @@ -392,25 +610,30 @@ internal class Av1SymbolEncoder : IDisposable w.WriteSymbol((int)chromaMode, this.uvMode[cflAllowed][(int)lumaMode]); } + /// + /// Writes the joint chroma-from-luma signs and the magnitude index for each nonzero plane. + /// + /// The packed U/V alpha-magnitude indices. + /// The joint U/V sign symbol. internal void WriteChromaFromLumaAlphas(int chromaFromLumaIndex, int joinedSign) { ref Av1SymbolWriter w = ref this.writer; w.WriteSymbol(joinedSign, this.chromaFromLumaSign); - // Magnitudes are only signaled for nonzero codes. - int signU = ((joinedSign + 1) * 11) >> 5; - if (signU != 0) + // Magnitudes are only signaled for nonzero signs; the shared helper keeps encoder and decoder mappings exact. + int signU = Av1ChromaFromLumaMath.SignU(joinedSign); + if (signU != Av1ChromaFromLumaMath.SignZero) { - int contextU = chromaFromLumaIndex - 2; - int indexU = chromaFromLumaIndex >> Av1Constants.ChromaFromLumaAlphabetSizeLog2; + int contextU = Av1ChromaFromLumaMath.ContextU(joinedSign); + int indexU = Av1ChromaFromLumaMath.IndexU(chromaFromLumaIndex); w.WriteSymbol(indexU, this.chromaFromLumaAlpha[contextU]); } - int signV = (joinedSign + 1) - (3 * signU); - if (signV != 0) + int signV = Av1ChromaFromLumaMath.SignV(joinedSign); + if (signV != Av1ChromaFromLumaMath.SignZero) { - int contextV = (signV * 3) - signU - 3; - int indexV = chromaFromLumaIndex & ((1 << Av1Constants.ChromaFromLumaAlphabetSizeLog2) - 1); + int contextV = Av1ChromaFromLumaMath.ContextV(joinedSign); + int indexV = Av1ChromaFromLumaMath.IndexV(chromaFromLumaIndex); w.WriteSymbol(indexV, this.chromaFromLumaAlpha[contextV]); } }