Browse Source

Document and correct AV1 coefficient entropy

pull/2633/head
James Jackson-South 1 week ago
parent
commit
635f3380da
  1. 138
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs
  2. 130
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs
  3. 36
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
  4. 438
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
  5. 297
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs

138
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs

@ -3,8 +3,14 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
/// <summary>
/// Provides the normative initial AV1 cumulative distributions used before tile-local adaptation.
/// </summary>
internal static class Av1DefaultDistributions internal static class Av1DefaultDistributions
{ {
/// <summary>
/// Gets the intra-frame luma-mode distributions indexed by block-size group.
/// </summary>
public static Av1Distribution[] FrameYMode => public static Av1Distribution[] FrameYMode =>
[ [
new(22801, 23489, 24293, 24756, 25601, 26123, 26606, 27418, 27945, 29228, 29685, 30349), 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) new(20155, 21301, 22838, 23178, 23261, 23533, 23703, 24804, 25352, 26575, 27016, 28049)
]; ];
/// <summary>
/// Gets the key-frame luma-mode distributions indexed by the above and left intra-mode contexts.
/// </summary>
public static Av1Distribution[][] FilterYMode => public static Av1Distribution[][] FilterYMode =>
[ [
[ [
@ -48,6 +57,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the chroma intra-mode distributions indexed first by chroma-from-luma availability and then by luma mode.
/// </summary>
public static Av1Distribution[][] UvMode => public static Av1Distribution[][] UvMode =>
[ [
[ [
@ -81,6 +93,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the directional intra-prediction angle-delta distributions indexed by directional mode.
/// </summary>
public static Av1Distribution[] AngleDelta => public static Av1Distribution[] AngleDelta =>
[ [
new(2180, 5032, 7567, 22776, 26989, 30217), new(2180, 5032, 7567, 22776, 26989, 30217),
@ -93,8 +108,14 @@ internal static class Av1DefaultDistributions
new(3605, 10428, 12459, 17676, 21244, 30655) new(3605, 10428, 12459, 17676, 21244, 30655)
]; ];
/// <summary>
/// Gets the binary distribution that signals intra-block copy.
/// </summary>
public static Av1Distribution IntraBlockCopy => new(30531); public static Av1Distribution IntraBlockCopy => new(30531);
/// <summary>
/// Gets the partition-type distributions indexed by block-size and neighboring split context.
/// </summary>
public static Av1Distribution[] PartitionTypes => public static Av1Distribution[] PartitionTypes =>
[ [
new(19132, 25510, 30392), new(19132, 25510, 30392),
@ -120,25 +141,28 @@ internal static class Av1DefaultDistributions
]; ];
/// <summary> /// <summary>
/// Gets the skip <see cref="Av1Distribution"/>. /// Gets the transform-skip distributions indexed by the neighboring skip context.
/// </summary> /// </summary>
/// <remarks>SVT: default_skip_cdfs</remarks>
public static Av1Distribution[] Skip => [new(31671), new(16515), new(4576)]; public static Av1Distribution[] Skip => [new(31671), new(16515), new(4576)];
/// <summary> /// <summary>
/// Gets the skip mode <see cref="Av1Distribution"/>. /// Gets the skip-mode distributions indexed by the neighboring skip-mode context.
/// </summary> /// </summary>
/// <remarks>SVT: default_skip_mode_cdfs</remarks>
public static Av1Distribution[] SkipMode => [new(32621), new(20708), new(8127)]; public static Av1Distribution[] SkipMode => [new(32621), new(20708), new(8127)];
/// <summary>
/// Gets the distribution for an absolute loop-filter delta magnitude.
/// </summary>
public static Av1Distribution DeltaLoopFilterAbsolute => new(28160, 32120, 32677); public static Av1Distribution DeltaLoopFilterAbsolute => new(28160, 32120, 32677);
/// <summary>
/// Gets the distribution for an absolute quantizer delta magnitude.
/// </summary>
public static Av1Distribution DeltaQuantizerAbsolute => new(28160, 32120, 32677); public static Av1Distribution DeltaQuantizerAbsolute => new(28160, 32120, 32677);
/// <summary> /// <summary>
/// Gets the Segment identifier <see cref="Av1Distribution"/>. /// Gets the spatially predicted segment-identifier distributions indexed by neighboring segment context.
/// </summary> /// </summary>
/// <remarks>SVT: default_spatial_pred_seg_tree_cdf</remarks>
public static Av1Distribution[] SegmentId => public static Av1Distribution[] SegmentId =>
[ [
new(5622, 7893, 16093, 18233, 27809, 28373, 32533), new(5622, 7893, 16093, 18233, 27809, 28373, 32533),
@ -146,6 +170,9 @@ internal static class Av1DefaultDistributions
new(27527, 28487, 28723, 28890, 32397, 32647, 32679), new(27527, 28487, 28723, 28890, 32397, 32647, 32679),
]; ];
/// <summary>
/// Gets the key-frame luma intra-mode distributions indexed by the above and left mode contexts.
/// </summary>
public static Av1Distribution[][] KeyFrameYMode => public static Av1Distribution[][] KeyFrameYMode =>
[ [
[ [
@ -181,8 +208,14 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the distribution selecting a filter-intra prediction mode.
/// </summary>
public static Av1Distribution FilterIntraMode => new(8949, 12776, 17211, 29558); public static Av1Distribution FilterIntraMode => new(8949, 12776, 17211, 29558);
/// <summary>
/// Gets the binary filter-intra enable distributions indexed by block size.
/// </summary>
public static Av1Distribution[] FilterIntra => public static Av1Distribution[] FilterIntra =>
[ [
new(4621), new(6743), new(5893), new(7866), new(12551), new(9394), 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) new(20229), new(18101), new(16384), new(16384)
]; ];
/// <summary>
/// Gets the transform-size distributions indexed by maximum transform category and neighboring transform-size context.
/// </summary>
public static Av1Distribution[][] TransformSize => public static Av1Distribution[][] TransformSize =>
[ [
[new(19968), new(19968), new(24320)], [new(19968), new(19968), new(24320)],
@ -199,6 +235,9 @@ internal static class Av1DefaultDistributions
[new(5782, 11475), new(5782, 11475), new(16803, 22759)], [new(5782, 11475), new(5782, 11475), new(16803, 22759)],
]; ];
/// <summary>
/// Gets the end-of-block token distributions for transforms containing at most 16 coefficients, indexed by quantizer, plane, and transform-class contexts.
/// </summary>
private static Av1Distribution[][][] EndOfBlockFlagMulti16 => private static Av1Distribution[][][] EndOfBlockFlagMulti16 =>
[ [
[ [
@ -219,6 +258,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the end-of-block token distributions for transforms containing at most 32 coefficients, indexed by quantizer, plane, and transform-class contexts.
/// </summary>
private static Av1Distribution[][][] EndOfBlockFlagMulti32 => private static Av1Distribution[][][] EndOfBlockFlagMulti32 =>
[ [
[ [
@ -239,6 +281,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the end-of-block token distributions for transforms containing at most 64 coefficients, indexed by quantizer, plane, and transform-class contexts.
/// </summary>
private static Av1Distribution[][][] EndOfBlockFlagMulti64 => private static Av1Distribution[][][] EndOfBlockFlagMulti64 =>
[ [
[ [
@ -259,6 +304,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the end-of-block token distributions for transforms containing at most 128 coefficients, indexed by quantizer, plane, and transform-class contexts.
/// </summary>
private static Av1Distribution[][][] EndOfBlockFlagMulti128 => private static Av1Distribution[][][] EndOfBlockFlagMulti128 =>
[ [
[ [
@ -279,6 +327,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the end-of-block token distributions for transforms containing at most 256 coefficients, indexed by quantizer, plane, and transform-class contexts.
/// </summary>
private static Av1Distribution[][][] EndOfBlockFlagMulti256 => private static Av1Distribution[][][] EndOfBlockFlagMulti256 =>
[ [
[ [
@ -323,6 +374,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the end-of-block token distributions for transforms containing at most 512 coefficients, indexed by quantizer, plane, and transform-class contexts.
/// </summary>
private static Av1Distribution[][][] EndOfBlockFlagMulti512 => private static Av1Distribution[][][] EndOfBlockFlagMulti512 =>
[ [
[ [
@ -367,6 +421,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the end-of-block token distributions for transforms containing at most 1024 coded coefficients, indexed by quantizer, plane, and transform-class contexts.
/// </summary>
private static Av1Distribution[][][] EndOfBlockFlagMulti1024 => private static Av1Distribution[][][] EndOfBlockFlagMulti1024 =>
[ [
[ [
@ -411,6 +468,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the coefficient base-range distributions indexed by quantizer, transform-size, plane, and base-range contexts.
/// </summary>
private static Av1Distribution[][][][] CoefficientsBaseRange => private static Av1Distribution[][][][] CoefficientsBaseRange =>
[ [
[ [
@ -823,6 +883,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the coefficient base-level distributions indexed by quantizer, transform-size, plane, and nonzero-map contexts.
/// </summary>
private static Av1Distribution[][][][] CoefficientsBase => private static Av1Distribution[][][][] CoefficientsBase =>
[ [
[ [
@ -1515,6 +1578,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the final-nonzero coefficient base-level distributions indexed by quantizer, transform-size, plane, and end-of-block contexts.
/// </summary>
private static Av1Distribution[][][][] BaseEndOfBlock => private static Av1Distribution[][][][] BaseEndOfBlock =>
[ [
[ [
@ -1607,6 +1673,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the DC coefficient-sign distributions indexed by quantizer, plane, and neighboring sign contexts.
/// </summary>
private static Av1Distribution[][][] DcSign => private static Av1Distribution[][][] DcSign =>
[ [
[ [
@ -1627,7 +1696,9 @@ internal static class Av1DefaultDistributions
], ],
]; ];
// SVT: av1_default_txb_skip_cdfs /// <summary>
/// Gets the transform-block skip distributions indexed by quantizer, transform-size, and neighboring skip contexts.
/// </summary>
private static Av1Distribution[][][] TransformBlockSkip => private static Av1Distribution[][][] TransformBlockSkip =>
[ [
[ [
@ -1720,6 +1791,9 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the end-of-block extra-bit distributions indexed by quantizer, transform-size, plane, and token contexts.
/// </summary>
private static Av1Distribution[][][][] EndOfBlockExtra => private static Av1Distribution[][][][] EndOfBlockExtra =>
[ [
[ [
@ -2012,8 +2086,14 @@ internal static class Av1DefaultDistributions
] ]
]; ];
/// <summary>
/// Gets the joint chroma-from-luma sign distribution for the U and V alpha values.
/// </summary>
public static Av1Distribution ChromaFromLumaSign => new(1418, 2123, 13340, 18405, 26972, 28343, 32294); public static Av1Distribution ChromaFromLumaSign => new(1418, 2123, 13340, 18405, 26972, 28343, 32294);
/// <summary>
/// Gets the chroma-from-luma alpha-magnitude distributions indexed by joint-sign context.
/// </summary>
public static Av1Distribution[] ChromaFromLumaAlpha => public static Av1Distribution[] ChromaFromLumaAlpha =>
[ [
new(7637, 20719, 31401, 32481, 32657, 32688, 32692, 32696, 32700, 32704, 32708, 32712, 32716, 32720, 32724), 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) new(14738, 21678, 25779, 27901, 29024, 30302, 30980, 31843, 32144, 32413, 32520, 32594, 32622, 32656, 32660)
]; ];
/// <summary>
/// Gets the intra transform-type distributions indexed by transform set, transform-size group, and intra prediction mode.
/// </summary>
public static Av1Distribution[][][] IntraExtendedTransform => public static Av1Distribution[][][] IntraExtendedTransform =>
[ [
[ [
@ -2214,8 +2297,14 @@ internal static class Av1DefaultDistributions
], ],
]; ];
/// <summary>
/// Gets the complete set of end-of-block token distributions for a base quantizer.
/// </summary>
/// <param name="baseQIndex">The frame base quantizer index.</param>
/// <returns>The distributions indexed by coefficient-count category, plane, and transform-class contexts.</returns>
public static Av1Distribution[][][] GetEndOfBlockFlag(int baseQIndex) 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); int qContext = GetQContext(baseQIndex);
return return
[ [
@ -2229,24 +2318,59 @@ internal static class Av1DefaultDistributions
]; ];
} }
/// <summary>
/// Gets the coefficient base-range distributions for a base quantizer.
/// </summary>
/// <param name="baseQIndex">The frame base quantizer index.</param>
/// <returns>The distributions indexed by transform-size, plane, and base-range contexts.</returns>
public static Av1Distribution[][][] GetCoefficientsBaseRange(int baseQIndex) public static Av1Distribution[][][] GetCoefficientsBaseRange(int baseQIndex)
=> CoefficientsBaseRange[GetQContext(baseQIndex)]; => CoefficientsBaseRange[GetQContext(baseQIndex)];
/// <summary>
/// Gets the coefficient base-level distributions for a base quantizer.
/// </summary>
/// <param name="baseQIndex">The frame base quantizer index.</param>
/// <returns>The distributions indexed by transform-size, plane, and nonzero-map contexts.</returns>
public static Av1Distribution[][][] GetCoefficientsBase(int baseQIndex) public static Av1Distribution[][][] GetCoefficientsBase(int baseQIndex)
=> CoefficientsBase[GetQContext(baseQIndex)]; => CoefficientsBase[GetQContext(baseQIndex)];
/// <summary>
/// Gets the final-nonzero coefficient base-level distributions for a base quantizer.
/// </summary>
/// <param name="baseQIndex">The frame base quantizer index.</param>
/// <returns>The distributions indexed by transform-size, plane, and end-of-block contexts.</returns>
public static Av1Distribution[][][] GetBaseEndOfBlock(int baseQIndex) public static Av1Distribution[][][] GetBaseEndOfBlock(int baseQIndex)
=> BaseEndOfBlock[GetQContext(baseQIndex)]; => BaseEndOfBlock[GetQContext(baseQIndex)];
/// <summary>
/// Gets the DC sign distributions for a base quantizer.
/// </summary>
/// <param name="baseQIndex">The frame base quantizer index.</param>
/// <returns>The distributions indexed by plane and neighboring sign context.</returns>
public static Av1Distribution[][] GetDcSign(int baseQIndex) public static Av1Distribution[][] GetDcSign(int baseQIndex)
=> DcSign[GetQContext(baseQIndex)]; => DcSign[GetQContext(baseQIndex)];
/// <summary>
/// Gets the transform-block skip distributions for a base quantizer.
/// </summary>
/// <param name="baseQIndex">The frame base quantizer index.</param>
/// <returns>The distributions indexed by transform-size and neighboring skip context.</returns>
public static Av1Distribution[][] GetTransformBlockSkip(int baseQIndex) public static Av1Distribution[][] GetTransformBlockSkip(int baseQIndex)
=> TransformBlockSkip[GetQContext(baseQIndex)]; => TransformBlockSkip[GetQContext(baseQIndex)];
/// <summary>
/// Gets the end-of-block extra-bit distributions for a base quantizer.
/// </summary>
/// <param name="baseQIndex">The frame base quantizer index.</param>
/// <returns>The distributions indexed by transform-size, plane, and end-of-block token context.</returns>
public static Av1Distribution[][][] GetEndOfBlockExtra(int baseQIndex) public static Av1Distribution[][][] GetEndOfBlockExtra(int baseQIndex)
=> EndOfBlockExtra[GetQContext(baseQIndex)]; => EndOfBlockExtra[GetQContext(baseQIndex)];
/// <summary>
/// Maps a base quantizer index to one of the four AV1 coefficient-probability initialization bands.
/// </summary>
/// <param name="q">The base quantizer index.</param>
/// <returns>The zero-based quantizer context.</returns>
private static int GetQContext(int q) private static int GetQContext(int q)
{ {
if (q <= 20) if (q <= 20)

130
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; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
/// <summary>
/// Derives the AV1 nonzero-coefficient probability context from neighboring coefficient levels and transform geometry.
/// </summary>
internal static class Av1NzMap internal static class Av1NzMap
{ {
// SIG_COEF_CONTEXTS_2D = 26 /// <summary>
/// The first one-dimensional nonzero-map context, immediately after the 26 two-dimensional contexts.
/// </summary>
private const int NzMapContext0 = 26; private const int NzMapContext0 = 26;
/// <summary>
/// The second one-dimensional position band.
/// </summary>
private const int NzMapContext5 = NzMapContext0 + 5; private const int NzMapContext5 = NzMapContext0 + 5;
/// <summary>
/// The final one-dimensional position band.
/// </summary>
private const int NzMapContext10 = NzMapContext0 + 10; private const int NzMapContext10 = NzMapContext0 + 10;
/// <summary>
/// Maps a horizontal or vertical coefficient coordinate to its one-dimensional context offset.
/// </summary>
private static readonly int[] NzMapContextOffset1d = [ private static readonly int[] NzMapContextOffset1d = [
NzMapContext0, NzMapContext5, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext0, NzMapContext5, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
@ -21,14 +37,18 @@ internal static class Av1NzMap
NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
]; ];
// The ctx offset table when TX is TX_CLASS_2D. /// <summary>
// TX col and row indices are clamped to 4 /// The row-major positional context offsets for a 4x4 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset4x4 = [ private static readonly int[] NzMapContextOffset4x4 = [
0, 1, 6, 6, 0, 1, 6, 6,
1, 6, 6, 21, 1, 6, 6, 21,
6, 6, 21, 21, 6, 6, 21, 21,
6, 21, 21, 21]; 6, 21, 21, 21];
/// <summary>
/// The row-major positional context offsets for an 8x8 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset8x8 = [ private static readonly int[] NzMapContextOffset8x8 = [
0, 1, 6, 6, 21, 21, 21, 21, 0, 1, 6, 6, 21, 21, 21, 21,
1, 6, 6, 21, 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, 21, 21, 21, 21, 21, 21, 21, 21,
]; ];
/// <summary>
/// The row-major positional context offsets for a 16x16 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset16x16 = [ private static readonly int[] NzMapContextOffset16x16 = [
0, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 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, 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, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
]; ];
/// <summary>
/// The row-major positional context offsets for a 32x32 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset32x32 = [ 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, 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, 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, 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,
]; ];
/// <summary>
/// The row-major positional context offsets for an 8x4 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset8x4 = [ private static readonly int[] NzMapContextOffset8x4 = [
0, 16, 6, 6, 21, 21, 21, 21, 0, 16, 6, 6, 21, 21, 21, 21,
16, 16, 6, 21, 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, 16, 16, 21, 21, 21, 21, 21, 21,
]; ];
/// <summary>
/// The row-major positional context offsets for a 16x8 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset16x8 = [ private static readonly int[] NzMapContextOffset16x8 = [
0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 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, 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, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
]; ];
/// <summary>
/// The row-major positional context offsets for a 16x32 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset16x32 = [ private static readonly int[] NzMapContextOffset16x32 = [
0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 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,
@ -147,6 +182,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,
]; ];
/// <summary>
/// The row-major positional context offsets for a 32x16 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset32x16 = [ 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, 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, 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, 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,
]; ];
/// <summary>
/// The row-major positional context offsets for the coded low-frequency region of a 32x64 transform.
/// </summary>
private static readonly int[] NzMapContextOffset32x64 = [ 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, 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, 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, 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,
]; ];
/// <summary>
/// The row-major positional context offsets for the coded low-frequency region of a 64x32 transform.
/// </summary>
private static readonly int[] NzMapContextOffset64x32 = [ 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, 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, 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, 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,
]; ];
/// <summary>
/// The row-major positional context offsets for a 4x16 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset4x16 = [ private static readonly int[] NzMapContextOffset4x16 = [
0, 11, 11, 11, 0, 11, 11, 11,
11, 11, 11, 11, 11, 11, 11, 11,
@ -255,6 +302,9 @@ internal static class Av1NzMap
21, 21, 21, 21, 21, 21, 21, 21,
]; ];
/// <summary>
/// The row-major positional context offsets for a 16x4 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset16x4 = [ private static readonly int[] NzMapContextOffset16x4 = [
0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 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, 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, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
]; ];
/// <summary>
/// The row-major positional context offsets for an 8x32 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset8x32 = [ private static readonly int[] NzMapContextOffset8x32 = [
0, 11, 11, 11, 11, 11, 11, 11, 0, 11, 11, 11, 11, 11, 11, 11,
11, 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, 21, 21, 21, 21, 21, 21, 21, 21,
]; ];
/// <summary>
/// The row-major positional context offsets for a 32x8 two-dimensional transform.
/// </summary>
private static readonly int[] NzMapContextOffset32x8 = [ 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, 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, 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, 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,
]; ];
/// <summary>
/// Maps each AV1 transform-size value to its row-major two-dimensional positional context table.
/// </summary>
private static readonly int[][] NzMapContextOffset = [ 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 NzMapContextOffset4x4, // TX_4x4
NzMapContextOffset8x8, // TX_8x8 NzMapContextOffset8x8, // TX_8x8
NzMapContextOffset16x16, // TX_16x16 NzMapContextOffset16x16, // TX_16x16
NzMapContextOffset32x32, // TX_32x32 NzMapContextOffset32x32, // TX_32x32
NzMapContextOffset32x32, // TX_32x32 NzMapContextOffset32x32, // TX_64x64
NzMapContextOffset4x16, // TX_4x8 NzMapContextOffset4x16, // TX_4x8
NzMapContextOffset8x4, // TX_8x4 NzMapContextOffset8x4, // TX_8x4
NzMapContextOffset8x32, // TX_8x16 NzMapContextOffset8x32, // TX_8x16
@ -331,8 +393,12 @@ internal static class Av1NzMap
]; ];
/// <summary> /// <summary>
/// SVT: get_nz_mag /// Sums the clipped magnitudes of the transform-class-specific forward coefficient neighbors.
/// </summary> /// </summary>
/// <param name="levels">The padded absolute-coefficient level plane.</param>
/// <param name="position">The coefficient position.</param>
/// <param name="transformClass">The transform direction class selecting the neighbor pattern.</param>
/// <returns>The summed neighbor magnitude used to select a nonzero-map context.</returns>
public static int GetNzMagnitude(Av1LevelBuffer levels, Point position, Av1TransformClass transformClass) public static int GetNzMagnitude(Av1LevelBuffer levels, Point position, Av1TransformClass transformClass)
{ {
int mag; int mag;
@ -340,7 +406,7 @@ internal static class Av1NzMap
Span<byte> row1 = levels.GetRow(position.Y + 1)[position.X..]; Span<byte> row1 = levels.GetRow(position.Y + 1)[position.X..];
Span<byte> row2 = levels.GetRow(position.Y + 2)[position.X..]; Span<byte> 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(row0[1]); // { 0, 1 }
mag += ClipMax3(row1[0]); // { 1, 0 } mag += ClipMax3(row1[0]); // { 1, 0 }
@ -369,30 +435,29 @@ internal static class Av1NzMap
return mag; return mag;
} }
/// <summary>
/// Combines a neighboring-level statistic with the coefficient's transform-class-specific position band.
/// </summary>
/// <param name="stats">The clipped sum of the applicable forward-neighbor magnitudes.</param>
/// <param name="position">The coefficient position.</param>
/// <param name="transformSize">The coded transform size selecting the positional table.</param>
/// <param name="transformClass">The transform direction class.</param>
/// <returns>The nonzero-map probability context.</returns>
public static int GetNzMapContextFromStats(int stats, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass) public static int GetNzMapContextFromStats(int stats, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass)
{ {
// tx_class == 0(TX_CLASS_2D) // The DC coefficient has a dedicated 2D context independent of neighboring levels.
if (transformClass == 0 && (position.X == 0) && (position.Y == 0)) if (transformClass == Av1TransformClass.Class2D && position.X == 0 && position.Y == 0)
{ {
return 0; return 0;
} }
// Rounding the neighbor sum before clipping produces the five AV1 magnitude bands 0 through 4.
int ctx = (stats + 1) >> 1; int ctx = (stats + 1) >> 1;
ctx = Math.Min(ctx, 4); ctx = Math.Min(ctx, 4);
switch (transformClass) switch (transformClass)
{ {
case Av1TransformClass.Class2D: case Av1TransformClass.Class2D:
// This is the algorithm to generate eb_av1_nz_map_ctx_offset[][] // The tables preserve AV1's distinct early-row and early-column bands for rectangular transforms.
// 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;
return ctx + GetNzMapContext(transformSize, position); return ctx + GetNzMapContext(transformSize, position);
case Av1TransformClass.ClassHorizontal: case Av1TransformClass.ClassHorizontal:
return ctx + NzMapContextOffset1d[position.X]; return ctx + NzMapContextOffset1d[position.X];
@ -405,9 +470,32 @@ internal static class Av1NzMap
return 0; return 0;
} }
public static int GetNzMapContext(Av1TransformSize transformSize, Point pos) => GetNzMapContext(transformSize, pos.X + (pos.Y * transformSize.GetWidth())); /// <summary>
/// Gets the two-dimensional positional context offset for a coefficient position.
/// </summary>
/// <param name="transformSize">The coded transform size.</param>
/// <param name="position">The coefficient position.</param>
/// <returns>The positional context offset.</returns>
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]; /// <summary>
/// Gets the two-dimensional positional context offset for a row-major coefficient index.
/// </summary>
/// <param name="transformSize">The coded transform size.</param>
/// <param name="position">The row-major coefficient index.</param>
/// <returns>The positional context offset.</returns>
public static int GetNzMapContext(Av1TransformSize transformSize, int position) => NzMapContextOffset[(int)transformSize][position];
/// <summary>
/// Clips a coefficient magnitude to the maximum contribution allowed per neighbor.
/// </summary>
/// <param name="value">The coefficient magnitude.</param>
/// <returns>The magnitude limited to three.</returns>
private static int ClipMax3(int value) => Math.Min(value, 3); private static int ClipMax3(int value) => Math.Min(value, 3);
} }

36
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs

@ -111,26 +111,34 @@ internal static class Av1SymbolContextHelper
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="levels">The padded coefficient-level buffer.</param> /// <param name="levels">The padded coefficient-level buffer.</param>
/// <param name="position">The coefficient position in raster order.</param> /// <param name="position">The coordinate whose row-major index identifies the coefficient's scan position.</param>
/// <returns>The end-of-block lower-level context.</returns> /// <returns>The end-of-block lower-level context.</returns>
internal static int GetLowerLevelContextEndOfBlock(Av1LevelBuffer levels, Point position) internal static int GetLowerLevelContextEndOfBlock(Av1LevelBuffer levels, Point position)
=> GetLowerLevelContextEndOfBlock(levels, position.X + (position.Y * levels.Size.Width));
/// <summary>
/// Derives the lower-level context for the final nonzero coefficient from its scan-order index.
/// </summary>
/// <param name="levels">The padded coefficient-level buffer.</param>
/// <param name="scanIndex">The zero-based coefficient index in scan order.</param>
/// <returns>The end-of-block lower-level context.</returns>
internal static int GetLowerLevelContextEndOfBlock(Av1LevelBuffer levels, int scanIndex)
{ {
if (position.X == 0 && position.Y == 0) if (scanIndex == 0)
{ {
return 0; return 0;
} }
int total = levels.Size.Height * levels.Size.Width; int total = levels.Size.Height * levels.Size.Width;
int index = position.X + (position.Y * levels.Size.Width); if (scanIndex <= total >> 3)
if (index <= total >> 3)
{ {
return 1; return 1;
} }
if (index <= total >> 2) if (scanIndex <= total >> 2)
{ {
return 2; return 2;
} }
@ -335,26 +343,19 @@ internal static class Av1SymbolContextHelper
} }
/// <summary> /// <summary>
/// Derives the nonzero-map context for one coefficient. /// Derives the nonzero-map context for one coefficient preceding the final nonzero coefficient.
/// </summary> /// </summary>
/// <param name="levels">The padded coefficient-level buffer.</param> /// <param name="levels">The padded coefficient-level buffer.</param>
/// <param name="position">The coefficient position in raster order.</param> /// <param name="position">The coefficient position in raster order.</param>
/// <param name="isEndOfBlock">Indicates that this is the final nonzero coefficient.</param>
/// <param name="transformSize">The coded transform size.</param> /// <param name="transformSize">The coded transform size.</param>
/// <param name="transformClass">The transform direction class.</param> /// <param name="transformClass">The transform direction class.</param>
/// <returns>The nonzero-map context.</returns> /// <returns>The nonzero-map context.</returns>
internal static sbyte GetNzMapContext( internal static sbyte GetNzMapContext(
Av1LevelBuffer levels, Av1LevelBuffer levels,
Point position, Point position,
bool isEndOfBlock,
Av1TransformSize transformSize, Av1TransformSize transformSize,
Av1TransformClass transformClass) Av1TransformClass transformClass)
{ {
if (isEndOfBlock)
{
return (sbyte)GetLowerLevelContextEndOfBlock(levels, position);
}
int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass); int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass);
return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, position, transformSize, transformClass); return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, position, transformSize, transformClass);
} }
@ -380,7 +381,12 @@ internal static class Av1SymbolContextHelper
{ {
int pos = scan[i]; int pos = scan[i];
Point position = levels.GetPosition(pos); 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);
} }
} }

438
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs

@ -2,43 +2,158 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; 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.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
/// <summary>
/// Decodes tile syntax elements and transform coefficients from an AV1 entropy-coded bitstream.
/// </summary>
internal ref struct Av1SymbolDecoder internal ref struct Av1SymbolDecoder
{ {
/// <summary>
/// Maps each intra prediction mode to the reduced neighbor context used by key-frame luma modes.
/// </summary>
private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; 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];
/// <summary>
/// The tile-adaptive intra-block-copy distribution.
/// </summary>
private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
/// <summary>
/// The tile-adaptive partition-type distributions.
/// </summary>
private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes;
/// <summary>
/// The tile-adaptive key-frame luma-mode distributions.
/// </summary>
private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode; private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode;
/// <summary>
/// The tile-adaptive chroma intra-mode distributions.
/// </summary>
private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode; private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode;
/// <summary>
/// The tile-adaptive transform-skip distributions.
/// </summary>
private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip;
/// <summary>
/// The tile-adaptive skip-mode distributions.
/// </summary>
private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode; private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode;
/// <summary>
/// The tile-adaptive absolute loop-filter delta distribution.
/// </summary>
private readonly Av1Distribution deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute; private readonly Av1Distribution deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute;
/// <summary>
/// The tile-adaptive absolute quantizer delta distribution.
/// </summary>
private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute; private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute;
/// <summary>
/// The tile-adaptive spatial segment-identifier distributions.
/// </summary>
private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId; private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId;
/// <summary>
/// The tile-adaptive directional angle-delta distributions.
/// </summary>
private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta; private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta;
/// <summary>
/// The tile-adaptive filter-intra mode distribution.
/// </summary>
private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode; private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode;
/// <summary>
/// The tile-adaptive filter-intra enable distributions.
/// </summary>
private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra; private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra;
/// <summary>
/// The tile-adaptive transform-size distributions.
/// </summary>
private readonly Av1Distribution[][] transformSize = Av1DefaultDistributions.TransformSize; private readonly Av1Distribution[][] transformSize = Av1DefaultDistributions.TransformSize;
/// <summary>
/// The tile-adaptive end-of-block token distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][][] endOfBlockFlag; private readonly Av1Distribution[][][] endOfBlockFlag;
/// <summary>
/// The tile-adaptive coefficient base-level distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][][] coefficientsBase; private readonly Av1Distribution[][][] coefficientsBase;
/// <summary>
/// The tile-adaptive final-nonzero coefficient distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][][] baseEndOfBlock; private readonly Av1Distribution[][][] baseEndOfBlock;
/// <summary>
/// The tile-adaptive DC sign distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][] dcSign; private readonly Av1Distribution[][] dcSign;
/// <summary>
/// The tile-adaptive coefficient base-range distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][][] coefficientsBaseRange; private readonly Av1Distribution[][][] coefficientsBaseRange;
/// <summary>
/// The tile-adaptive transform-block skip distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][] transformBlockSkip; private readonly Av1Distribution[][] transformBlockSkip;
/// <summary>
/// The tile-adaptive end-of-block extra-bit distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][][] endOfBlockExtra; private readonly Av1Distribution[][][] endOfBlockExtra;
/// <summary>
/// The tile-adaptive joint chroma-from-luma sign distribution.
/// </summary>
private readonly Av1Distribution chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign; private readonly Av1Distribution chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign;
/// <summary>
/// The tile-adaptive chroma-from-luma alpha-magnitude distributions.
/// </summary>
private readonly Av1Distribution[] chromaFromLumaAlpha = Av1DefaultDistributions.ChromaFromLumaAlpha; private readonly Av1Distribution[] chromaFromLumaAlpha = Av1DefaultDistributions.ChromaFromLumaAlpha;
/// <summary>
/// The tile-adaptive intra transform-type distributions.
/// </summary>
private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform; private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform;
/// <summary>
/// The configuration providing temporary coefficient-context memory.
/// </summary>
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary>
/// The range decoder over the current tile payload.
/// </summary>
private Av1SymbolReader reader; private Av1SymbolReader reader;
/// <summary>
/// The frame base quantizer used to select coefficient probability models.
/// </summary>
private readonly int baseQIndex; private readonly int baseQIndex;
/// <summary>
/// Initializes a new instance of the <see cref="Av1SymbolDecoder"/> struct for one AV1 tile.
/// </summary>
/// <param name="configuration">The configuration providing temporary memory.</param>
/// <param name="tileData">The entropy-coded tile payload.</param>
/// <param name="qIndex">The frame base quantizer index.</param>
public Av1SymbolDecoder(Configuration configuration, Span<byte> tileData, int qIndex) public Av1SymbolDecoder(Configuration configuration, Span<byte> tileData, int qIndex)
{ {
this.configuration = configuration; this.configuration = configuration;
@ -53,18 +168,32 @@ internal ref struct Av1SymbolDecoder
this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex); this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex);
} }
/// <summary>
/// Reads a fixed-width CDEF strength index.
/// </summary>
/// <param name="bitCount">The number of bits signaled for the strength index.</param>
/// <returns>The decoded CDEF strength index.</returns>
public int ReadCdfStrength(int bitCount) public int ReadCdfStrength(int bitCount)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadLiteral(bitCount); return r.ReadLiteral(bitCount);
} }
/// <summary>
/// Reads the frame-local intra-block-copy flag.
/// </summary>
/// <returns><see langword="true"/> when intra-block copy is selected.</returns>
public bool ReadUseIntraBlockCopy() public bool ReadUseIntraBlockCopy()
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.tileIntraBlockCopy) > 0; return r.ReadSymbol(this.tileIntraBlockCopy) > 0;
} }
/// <summary>
/// Reads a complete block partition type from the selected partition context.
/// </summary>
/// <param name="context">The partition probability context.</param>
/// <returns>The decoded partition type.</returns>
public Av1PartitionType ReadPartitionType(int context) public Av1PartitionType ReadPartitionType(int context)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
@ -72,8 +201,11 @@ internal ref struct Av1SymbolDecoder
} }
/// <summary> /// <summary>
/// SVT: partition_gather_vert_alike /// Reads the binary split-versus-horizontal decision used at a clipped right tile boundary.
/// </summary> /// </summary>
/// <param name="blockSize">The current block size.</param>
/// <param name="context">The partition probability context.</param>
/// <returns><see cref="Av1PartitionType.Split"/> or <see cref="Av1PartitionType.Horizontal"/>.</returns>
public Av1PartitionType ReadSplitOrHorizontal(Av1BlockSize blockSize, int context) public Av1PartitionType ReadSplitOrHorizontal(Av1BlockSize blockSize, int context)
{ {
Av1Distribution distribution = GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context); Av1Distribution distribution = GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context);
@ -82,8 +214,11 @@ internal ref struct Av1SymbolDecoder
} }
/// <summary> /// <summary>
/// SVT: partition_gather_horz_alike /// Reads the binary split-versus-vertical decision used at a clipped bottom tile boundary.
/// </summary> /// </summary>
/// <param name="blockSize">The current block size.</param>
/// <param name="context">The partition probability context.</param>
/// <returns><see cref="Av1PartitionType.Split"/> or <see cref="Av1PartitionType.Vertical"/>.</returns>
public Av1PartitionType ReadSplitOrVertical(Av1BlockSize blockSize, int context) public Av1PartitionType ReadSplitOrVertical(Av1BlockSize blockSize, int context)
{ {
Av1Distribution distribution = GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, 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; return r.ReadSymbol(distribution) > 0 ? Av1PartitionType.Split : Av1PartitionType.Vertical;
} }
/// <summary>
/// Reads a key-frame luma prediction mode using the available above and left modes.
/// </summary>
/// <param name="aboveModeInfo">The above block mode, or <see langword="null"/> at the frame boundary.</param>
/// <param name="leftModeInfo">The left block mode, or <see langword="null"/> at the frame boundary.</param>
/// <returns>The decoded luma prediction mode.</returns>
public Av1PredictionMode ReadYMode(Av1BlockModeInfo? aboveModeInfo, Av1BlockModeInfo? leftModeInfo) public Av1PredictionMode ReadYMode(Av1BlockModeInfo? aboveModeInfo, Av1BlockModeInfo? leftModeInfo)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
@ -111,6 +252,12 @@ internal ref struct Av1SymbolDecoder
return (Av1PredictionMode)r.ReadSymbol(this.keyFrameYMode[aboveContext][leftContext]); return (Av1PredictionMode)r.ReadSymbol(this.keyFrameYMode[aboveContext][leftContext]);
} }
/// <summary>
/// Reads a chroma intra prediction mode conditioned on the luma mode and chroma-from-luma availability.
/// </summary>
/// <param name="mode">The decoded luma prediction mode.</param>
/// <param name="chromaFromLumaAllowed">Indicates whether chroma-from-luma is valid for the block.</param>
/// <returns>The decoded chroma prediction mode.</returns>
public Av1PredictionMode ReadIntraModeUv(Av1PredictionMode mode, bool chromaFromLumaAllowed) public Av1PredictionMode ReadIntraModeUv(Av1PredictionMode mode, bool chromaFromLumaAllowed)
{ {
int chromaForLumaIndex = chromaFromLumaAllowed ? 1 : 0; int chromaForLumaIndex = chromaFromLumaAllowed ? 1 : 0;
@ -118,18 +265,32 @@ internal ref struct Av1SymbolDecoder
return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)mode]); return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)mode]);
} }
/// <summary>
/// Reads the transform-skip flag from a neighboring skip context.
/// </summary>
/// <param name="ctx">The neighboring skip context.</param>
/// <returns><see langword="true"/> when the block contains no coded transform coefficients.</returns>
public bool ReadSkip(int ctx) public bool ReadSkip(int ctx)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.skip[ctx]) > 0; return r.ReadSymbol(this.skip[ctx]) > 0;
} }
public bool ReadSkipMode(Av1BlockSize blockSize) /// <summary>
/// Reads the compound-reference skip-mode flag.
/// </summary>
/// <param name="context">The neighboring skip-mode context.</param>
/// <returns><see langword="true"/> when skip mode is selected.</returns>
public bool ReadSkipMode(int context)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.skipMode[(int)blockSize]) > 0; return r.ReadSymbol(this.skipMode[context]) > 0;
} }
/// <summary>
/// Reads a signed loop-filter delta value.
/// </summary>
/// <returns>The decoded loop-filter delta.</returns>
public int ReadDeltaLoopFilter() public int ReadDeltaLoopFilter()
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
@ -151,8 +312,9 @@ internal ref struct Av1SymbolDecoder
} }
/// <summary> /// <summary>
/// SVT: read_delta_qindex /// Reads a signed quantizer-index delta value.
/// </summary> /// </summary>
/// <returns>The decoded quantizer-index delta.</returns>
public int ReadDeltaQuantizerIndex() public int ReadDeltaQuantizerIndex()
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
@ -173,18 +335,33 @@ internal ref struct Av1SymbolDecoder
return deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; return deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute;
} }
/// <summary>
/// Reads a spatially predicted segment identifier.
/// </summary>
/// <param name="context">The context derived from neighboring segment identifiers.</param>
/// <returns>The decoded segment identifier.</returns>
public int ReadSegmentId(int context) public int ReadSegmentId(int context)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.segmentId[context]); return r.ReadSymbol(this.segmentId[context]);
} }
/// <summary>
/// Reads the unsigned directional angle-delta symbol for a prediction mode.
/// </summary>
/// <param name="mode">The directional prediction mode.</param>
/// <returns>The symbol in the range zero through twice the maximum signed angle delta.</returns>
public int ReadAngleDelta(Av1PredictionMode mode) public int ReadAngleDelta(Av1PredictionMode mode)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.angleDelta[(int)mode - 1]); return r.ReadSymbol(this.angleDelta[(int)mode - 1]);
} }
/// <summary>
/// Reads the filter-intra enable flag and, when enabled, its prediction mode.
/// </summary>
/// <param name="blockSize">The block size selecting the enable distribution.</param>
/// <returns>The selected mode, or <see cref="Av1FilterIntraMode.AllFilterIntraModes"/> when filter-intra is disabled.</returns>
public Av1FilterIntraMode ReadFilterUltraMode(Av1BlockSize blockSize) public Av1FilterIntraMode ReadFilterUltraMode(Av1BlockSize blockSize)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
@ -198,6 +375,12 @@ internal ref struct Av1SymbolDecoder
return filterIntraMode; return filterIntraMode;
} }
/// <summary>
/// Reads a transform subdivision depth and resolves it to a transform size.
/// </summary>
/// <param name="blockSize">The block size defining the maximum transform.</param>
/// <param name="context">The neighboring transform-size context.</param>
/// <returns>The decoded transform size.</returns>
public Av1TransformSize ReadTransformSize(Av1BlockSize blockSize, int context) public Av1TransformSize ReadTransformSize(Av1BlockSize blockSize, int context)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
@ -223,8 +406,15 @@ internal ref struct Av1SymbolDecoder
} }
/// <summary> /// <summary>
/// SVT: parse_transform_type /// Reads an intra transform type from the transform set permitted for the block.
/// </summary> /// </summary>
/// <param name="transformSize">The coded transform size.</param>
/// <param name="useReducedTransformSet">Indicates whether the frame restricts transform choices.</param>
/// <param name="useFilterIntra">Indicates whether filter-intra prediction selected the intra direction.</param>
/// <param name="baseQIndex">The active base quantizer index.</param>
/// <param name="filterIntraMode">The filter-intra mode when enabled.</param>
/// <param name="intraDirection">The ordinary intra prediction mode.</param>
/// <returns>The decoded transform type, or DCT-DCT when no transform type is signaled.</returns>
public Av1TransformType ReadTransformType( public Av1TransformType ReadTransformType(
Av1TransformSize transformSize, Av1TransformSize transformSize,
bool useReducedTransformSet, bool useReducedTransformSet,
@ -235,20 +425,13 @@ internal ref struct Av1SymbolDecoder
{ {
Av1TransformType transformType = Av1TransformType.DctDct; Av1TransformType transformType = Av1TransformType.DctDct;
/* // A zero base quantizer selects DCT-DCT and carries no transform-type symbol in this intra path.
// 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;
*/
if (baseQIndex == 0) if (baseQIndex == 0)
{ {
return transformType; return transformType;
} }
// Ignoring INTER blocks here, as these should not end up here. // Still-image decoding reaches this path only for intra blocks, so the intra transform set is authoritative.
// int inter_block = is_inter_block_dec(mbmi);
Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet); Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet);
if (transformSetType > Av1TransformSetType.DctOnly && baseQIndex > 0) if (transformSetType > Av1TransformSetType.DctOnly && baseQIndex > 0)
{ {
@ -265,35 +448,73 @@ internal ref struct Av1SymbolDecoder
return transformType; return transformType;
} }
/// <summary>
/// Reads whether a transform block has no coded coefficients.
/// </summary>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="skipContext">The context derived from neighboring coefficient blocks.</param>
/// <returns><see langword="true"/> when the transform block is empty.</returns>
public bool ReadTransformBlockSkip(Av1TransformSize transformSizeContext, int skipContext) public bool ReadTransformBlockSkip(Av1TransformSize transformSizeContext, int skipContext)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.transformBlockSkip[(int)transformSizeContext][skipContext]) > 0; return r.ReadSymbol(this.transformBlockSkip[(int)transformSizeContext][skipContext]) > 0;
} }
/// <summary>
/// Reads the joint U/V sign symbol for chroma-from-luma alpha values.
/// </summary>
/// <returns>The joint sign symbol.</returns>
public int ReadChromFromLumaSign() public int ReadChromFromLumaSign()
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.chromaFromLumaSign); return r.ReadSymbol(this.chromaFromLumaSign);
} }
/// <summary>
/// Reads the U-plane chroma-from-luma alpha-magnitude symbol.
/// </summary>
/// <param name="jointSignPlus1">The one-based joint U/V sign symbol.</param>
/// <returns>The U-plane alpha-magnitude symbol.</returns>
public int ReadChromaFromLumaAlphaU(int jointSignPlus1) public int ReadChromaFromLumaAlphaU(int jointSignPlus1)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
int context = jointSignPlus1 - 3; int context = Av1ChromaFromLumaMath.ContextU(jointSignPlus1 - 1);
return r.ReadSymbol(this.chromaFromLumaAlpha[context]); return r.ReadSymbol(this.chromaFromLumaAlpha[context]);
} }
/// <summary>
/// Reads the V-plane chroma-from-luma alpha-magnitude symbol.
/// </summary>
/// <param name="jointSignPlus1">The one-based joint U/V sign symbol.</param>
/// <returns>The V-plane alpha-magnitude symbol.</returns>
public int ReadChromaFromLumaAlphaV(int jointSignPlus1) public int ReadChromaFromLumaAlphaV(int jointSignPlus1)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
int context = AlphaVContexts[jointSignPlus1]; int context = Av1ChromaFromLumaMath.ContextV(jointSignPlus1 - 1);
return r.ReadSymbol(this.chromaFromLumaAlpha[context]); return r.ReadSymbol(this.chromaFromLumaAlpha[context]);
} }
/// <summary> /// <summary>
/// SVT: parse_coeffs /// Decodes one transform block's coefficient syntax and updates its neighboring entropy contexts.
/// </summary> /// </summary>
/// <param name="modeInfo">The current block prediction and segment modes.</param>
/// <param name="blockPosition">The transform-block position in four-sample units.</param>
/// <param name="aboveContexts">The above coefficient contexts for the current plane.</param>
/// <param name="leftContexts">The left coefficient contexts for the current plane.</param>
/// <param name="aboveOffset">The first above context covered by the transform.</param>
/// <param name="leftOffset">The first left context covered by the transform.</param>
/// <param name="plane">The zero-based Y, U, or V plane index.</param>
/// <param name="blocksWide">The available plane width in four-sample units.</param>
/// <param name="blocksHigh">The available plane height in four-sample units.</param>
/// <param name="transformBlockContext">The neighboring skip and DC sign contexts.</param>
/// <param name="transformSize">The signaled transform size.</param>
/// <param name="isLossless">Indicates whether the active segment is lossless.</param>
/// <param name="useReducedTransformSet">Indicates whether the frame restricts transform choices.</param>
/// <param name="transformInfo">The transform descriptor updated with the decoded type and coded-block flag.</param>
/// <param name="modeBlocksToRightEdge">The signed distance from the mode block to the right frame edge.</param>
/// <param name="modeBlocksToBottomEdge">The signed distance from the mode block to the bottom frame edge.</param>
/// <param name="coefficientBuffer">The destination receiving the coefficient count followed by scan-ordered signed levels.</param>
/// <returns>The one-based end-of-block position, or zero for an empty transform block.</returns>
public int ReadCoefficients( public int ReadCoefficients(
Av1BlockModeInfo modeInfo, Av1BlockModeInfo modeInfo,
Point blockPosition, Point blockPosition,
@ -313,13 +534,15 @@ internal ref struct Av1SymbolDecoder
int modeBlocksToBottomEdge, int modeBlocksToBottomEdge,
Span<int> coefficientBuffer) Span<int> coefficientBuffer)
{ {
int width = transformSize.GetWidth(); Av1TransformSize adjustedTransformSize = transformSize.GetAdjusted();
int height = transformSize.GetHeight(); int width = adjustedTransformSize.GetWidth();
int height = adjustedTransformSize.GetHeight();
Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize); Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize);
Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1);
int culLevel = 0; 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); bool allZero = this.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext);
int endOfBlock; int endOfBlock;
@ -373,6 +596,14 @@ internal ref struct Av1SymbolDecoder
return endOfBlock; return endOfBlock;
} }
/// <summary>
/// Reads an end-of-block token and its literal suffix.
/// </summary>
/// <param name="transformSize">The signaled transform size selecting the token alphabet.</param>
/// <param name="transformClass">The transform class selecting the two-dimensional or one-dimensional model.</param>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="planeType">The luma or chroma plane category.</param>
/// <returns>The one-based end-of-block coefficient position.</returns>
public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformClass transformClass, Av1TransformSize transformSizeContext, Av1PlaneType planeType) public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformClass transformClass, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
@ -381,7 +612,8 @@ internal ref struct Av1SymbolDecoder
int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint]; int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint];
if (endOfBlockShift > 0) 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); bool bit = this.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext);
if (bit) if (bit)
{ {
@ -400,13 +632,21 @@ internal ref struct Av1SymbolDecoder
return Av1SymbolContextHelper.RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); return Av1SymbolContextHelper.RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra);
} }
/// <summary>
/// Decodes the mandatory nonzero coefficient at the end-of-block scan position.
/// </summary>
/// <param name="transformClass">The transform direction class.</param>
/// <param name="endOfBlock">The one-based end-of-block position.</param>
/// <param name="scan">The transform's scan-to-raster mapping.</param>
/// <param name="levels">The padded absolute-coefficient level plane to update.</param>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="planeType">The luma or chroma plane category.</param>
public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{ {
int i = endOfBlock - 1; int i = endOfBlock - 1;
Point position = levels.GetPosition(scan[i]); 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; int level = this.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext) + 1;
Av1TransformSize limitedTransformSizeContext = (Av1TransformSize)Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32);
if (level > Av1Constants.BaseLevelsCount) if (level > Av1Constants.BaseLevelsCount)
{ {
int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContextEndOfBlock(position, transformClass); int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContextEndOfBlock(position, transformClass);
@ -416,9 +656,18 @@ internal ref struct Av1SymbolDecoder
levels.GetRow(position)[position.X] = (byte)level; levels.GetRow(position)[position.X] = (byte)level;
} }
/// <summary>
/// Decodes a reverse scan range using the specialized two-dimensional coefficient contexts.
/// </summary>
/// <param name="transformSize">The signaled transform size.</param>
/// <param name="startScanIndex">The inclusive lowest scan index.</param>
/// <param name="endScanIndex">The inclusive highest scan index.</param>
/// <param name="scan">The transform's scan-to-raster mapping.</param>
/// <param name="levels">The padded absolute-coefficient level plane to update.</param>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="planeType">The luma or chroma plane category.</param>
public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{ {
Av1TransformSize limitedTransformSizeContext = (Av1TransformSize)Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32);
for (int c = endScanIndex; c >= startScanIndex; --c) for (int c = endScanIndex; c >= startScanIndex; --c)
{ {
Point position = levels.GetPosition(scan[c]); Point position = levels.GetPosition(scan[c]);
@ -434,9 +683,19 @@ internal ref struct Av1SymbolDecoder
} }
} }
/// <summary>
/// Decodes a reverse scan range using transform-class-specific coefficient contexts.
/// </summary>
/// <param name="transformSize">The signaled transform size.</param>
/// <param name="transformClass">The transform direction class.</param>
/// <param name="startScanIndex">The inclusive lowest scan index.</param>
/// <param name="endScanIndex">The inclusive highest scan index.</param>
/// <param name="scan">The transform's scan-to-raster mapping.</param>
/// <param name="levels">The padded absolute-coefficient level plane to update.</param>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="planeType">The luma or chroma plane category.</param>
public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{ {
Av1TransformSize limitedTransformSizeContext = (Av1TransformSize)Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32);
for (int c = endScanIndex; c >= startScanIndex; --c) for (int c = endScanIndex; c >= startScanIndex; --c)
{ {
int pos = scan[c]; int pos = scan[c];
@ -453,21 +712,30 @@ internal ref struct Av1SymbolDecoder
} }
} }
/// <summary>
/// Reads coefficient signs and Golomb extensions, then writes scan-ordered signed levels.
/// </summary>
/// <param name="coefficientBuffer">The destination receiving the coefficient count followed by signed levels.</param>
/// <param name="endOfBlock">The one-based end-of-block position and coefficient count.</param>
/// <param name="scan">The transform's scan-to-raster mapping.</param>
/// <param name="levels">The decoded absolute-coefficient level plane.</param>
/// <param name="dcSignContext">The neighboring DC sign context.</param>
/// <param name="planeType">The luma or chroma plane category.</param>
/// <returns>The packed coefficient context used by adjacent transform blocks.</returns>
public int ReadCoefficientsSign(Span<int> coefficientBuffer, int endOfBlock, ReadOnlySpan<short> scan, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType) public int ReadCoefficientsSign(Span<int> coefficientBuffer, int endOfBlock, ReadOnlySpan<short> scan, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
int maxScanLine = 0;
int culLevel = 0; int culLevel = 0;
int dcValue = 0; int dcValue = 0;
coefficientBuffer[0] = endOfBlock; coefficientBuffer[0] = endOfBlock;
for (int c = 0; c < endOfBlock; c++) for (int c = 0; c < endOfBlock; c++)
{ {
int sign = 0; int sign = 0;
Point position = levels.GetPosition(c); int pos = scan[c];
Point position = levels.GetPosition(pos);
int level = levels[position]; int level = levels[position];
if (level != 0) if (level != 0)
{ {
maxScanLine = Math.Max(maxScanLine, scan[c]);
if (c == 0) if (c == 0)
{ {
sign = this.ReadDcSign(planeType, dcSignContext); sign = this.ReadDcSign(planeType, dcSignContext);
@ -500,6 +768,13 @@ internal ref struct Av1SymbolDecoder
return culLevel; return culLevel;
} }
/// <summary>
/// Reads the end-of-block token for a transform coefficient-count category.
/// </summary>
/// <param name="planeType">The luma or chroma plane category.</param>
/// <param name="transformClass">The transform direction class.</param>
/// <param name="transformSize">The signaled transform size.</param>
/// <returns>The one-based end-of-block token.</returns>
private int ReadEndOfBlockFlag(Av1PlaneType planeType, Av1TransformClass transformClass, Av1TransformSize transformSize) private int ReadEndOfBlockFlag(Av1PlaneType planeType, Av1TransformClass transformClass, Av1TransformSize transformSize)
{ {
int endOfBlockContext = transformClass == Av1TransformClass.Class2D ? 0 : 1; 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; return r.ReadSymbol(this.endOfBlockFlag[endOfBlockMultiSize][(int)planeType][endOfBlockContext]) + 1;
} }
/// <summary>
/// Reads the most significant context-coded bit of an end-of-block suffix.
/// </summary>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="planeType">The luma or chroma plane category.</param>
/// <param name="endOfBlockContext">The zero-based extra-bit token context.</param>
/// <returns>The decoded suffix bit.</returns>
private bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext) private bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.endOfBlockExtra[(int)transformSizeContext][(int)planeType][endOfBlockContext]) > 0; return r.ReadSymbol(this.endOfBlockExtra[(int)transformSizeContext][(int)planeType][endOfBlockContext]) > 0;
} }
/// <summary>
/// Reads one coefficient base-range symbol.
/// </summary>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="planeType">The luma or chroma plane category.</param>
/// <param name="baseRangeContext">The coefficient base-range context.</param>
/// <returns>The decoded base-range symbol.</returns>
private int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext) private int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.coefficientsBaseRange[(int)transformSizeContext][(int)planeType][baseRangeContext]); return r.ReadSymbol(this.coefficientsBaseRange[(int)transformSizeContext][(int)planeType][baseRangeContext]);
} }
/// <summary>
/// Reads the sign of a nonzero DC coefficient.
/// </summary>
/// <param name="planeType">The luma or chroma plane category.</param>
/// <param name="dcSignContext">The neighboring DC sign context.</param>
/// <returns>Zero for positive or one for negative.</returns>
private int ReadDcSign(Av1PlaneType planeType, int dcSignContext) private int ReadDcSign(Av1PlaneType planeType, int dcSignContext)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.dcSign[(int)planeType][dcSignContext]); return r.ReadSymbol(this.dcSign[(int)planeType][dcSignContext]);
} }
/// <summary>
/// Reads the base-level symbol for the final nonzero coefficient.
/// </summary>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="planeType">The luma or chroma plane category.</param>
/// <param name="coefficientContext">The end-of-block coefficient context.</param>
/// <returns>The zero-based base-level symbol.</returns>
private int ReadBaseEndOfBlock(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext) private int ReadBaseEndOfBlock(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.baseEndOfBlock[(int)transformSizeContext][(int)planeType][coefficientContext]); return r.ReadSymbol(this.baseEndOfBlock[(int)transformSizeContext][(int)planeType][coefficientContext]);
} }
/// <summary>
/// Reads the base-level symbol for a coefficient preceding end-of-block.
/// </summary>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="planeType">The luma or chroma plane category.</param>
/// <param name="coefficientContext">The nonzero-map coefficient context.</param>
/// <returns>The decoded base-level symbol.</returns>
private int ReadCoefficientsBase(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext) private int ReadCoefficientsBase(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]); return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]);
} }
/// <summary>
/// Accumulates coefficient base-range symbols until the terminal symbol or AV1 range limit is reached.
/// </summary>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="planeType">The luma or chroma plane category.</param>
/// <param name="baseRangeContext">The coefficient base-range context.</param>
/// <param name="level">The coefficient level to increment.</param>
private void ReadCoefficientsBaseRangeLoop(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext, ref int level) private void ReadCoefficientsBaseRangeLoop(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext, ref int level)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
@ -554,6 +870,11 @@ internal ref struct Av1SymbolDecoder
} }
} }
/// <summary>
/// Reads the unsigned exponential-Golomb suffix used for coefficient levels beyond the base range.
/// </summary>
/// <returns>The decoded nonnegative suffix value.</returns>
/// <exception cref="InvalidImageContentException">The unary prefix exceeds the AV1 coefficient limit.</exception>
internal int ReadGolomb() internal int ReadGolomb()
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
@ -567,8 +888,7 @@ internal ref struct Av1SymbolDecoder
++length; ++length;
if (length > 20) if (length > 20)
{ {
// SVT_LOG("Invalid length in read_golomb"); throw new InvalidImageContentException("The AV1 coefficient Golomb code exceeds its 20-bit limit.");
break;
} }
} }
@ -581,6 +901,21 @@ internal ref struct Av1SymbolDecoder
return x - 1; return x - 1;
} }
/// <summary>
/// Stores a transform block's packed coefficient context into the above and left neighbor arrays.
/// </summary>
/// <param name="modeInfo">The current block mode information.</param>
/// <param name="aboveContexts">The above contexts for the current plane.</param>
/// <param name="leftContexts">The left contexts for the current plane.</param>
/// <param name="blocksWide">The available plane width in four-sample units.</param>
/// <param name="blocksHigh">The available plane height in four-sample units.</param>
/// <param name="transformSize">The signaled transform size.</param>
/// <param name="blockPosition">The transform-block position in four-sample units.</param>
/// <param name="aboveOffset">The first above context covered by the transform.</param>
/// <param name="leftOffset">The first left context covered by the transform.</param>
/// <param name="culLevel">The packed coefficient magnitude and DC sign context.</param>
/// <param name="modeBlockToRightEdge">The signed distance from the mode block to the right frame edge.</param>
/// <param name="modeBlockToBottomEdge">The signed distance from the mode block to the bottom frame edge.</param>
private static void UpdateCoefficientContext( private static void UpdateCoefficientContext(
Av1BlockModeInfo modeInfo, Av1BlockModeInfo modeInfo,
int[] aboveContexts, int[] aboveContexts,
@ -613,7 +948,7 @@ internal ref struct Av1SymbolDecoder
{ {
int leftContextCount = Math.Min(transformSizeHigh, blocksHigh - leftOffset); int leftContextCount = Math.Min(transformSizeHigh, blocksHigh - leftOffset);
Array.Fill(leftContexts, culLevel, 0, leftContextCount); Array.Fill(leftContexts, culLevel, 0, leftContextCount);
Array.Fill(leftContexts, 0, leftContextCount, transformSizeWide - leftContextCount); Array.Fill(leftContexts, 0, leftContextCount, transformSizeHigh - leftContextCount);
} }
else else
{ {
@ -621,6 +956,16 @@ internal ref struct Av1SymbolDecoder
} }
} }
/// <summary>
/// Resolves the transform type permitted for a plane after lossless, size, prediction, and transform-set restrictions.
/// </summary>
/// <param name="planeType">The luma or chroma plane category.</param>
/// <param name="modeInfo">The current block prediction modes.</param>
/// <param name="isLossless">Indicates whether the active segment is lossless.</param>
/// <param name="transformSize">The signaled transform size.</param>
/// <param name="transformInfo">The transform descriptor containing the signaled luma type.</param>
/// <param name="useReducedTransformSet">Indicates whether the frame restricts transform choices.</param>
/// <returns>The transform type valid for the current plane.</returns>
private static Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1BlockModeInfo modeInfo, bool isLossless, Av1TransformSize transformSize, Av1TransformInfo transformInfo, bool useReducedTransformSet) private static Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1BlockModeInfo modeInfo, bool isLossless, Av1TransformSize transformSize, Av1TransformInfo transformInfo, bool useReducedTransformSet)
{ {
Av1TransformType transformType = Av1TransformType.DctDct; Av1TransformType transformType = Av1TransformType.DctDct;
@ -636,8 +981,7 @@ internal ref struct Av1SymbolDecoder
} }
else else
{ {
// In intra mode, uv planes don't share the same prediction mode as y // Chroma has its own intra mode, so its implicit transform must be derived independently of luma.
// plane, so the tx_type should not be shared
transformType = Av1SymbolContextHelper.ConvertIntraModeToTransformType(modeInfo, Av1PlaneType.Uv); transformType = Av1SymbolContextHelper.ConvertIntraModeToTransformType(modeInfo, Av1PlaneType.Uv);
} }
} }
@ -651,9 +995,18 @@ internal ref struct Av1SymbolDecoder
return transformType; return transformType;
} }
/// <summary>
/// Collapses a full partition distribution into the split-versus-horizontal boundary decision.
/// </summary>
/// <param name="inputs">The full partition distributions.</param>
/// <param name="blockSize">The current block size.</param>
/// <param name="context">The partition probability context.</param>
/// <returns>The binary cumulative distribution for split versus the horizontal-like partition group.</returns>
internal static Av1Distribution GetSplitOrHorizontalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) internal static Av1Distribution GetSplitOrHorizontalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context)
{ {
Av1Distribution input = inputs[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; uint p = Av1Distribution.ProbabilityTop;
p -= GetElementProbability(input, Av1PartitionType.Horizontal); p -= GetElementProbability(input, Av1PartitionType.Horizontal);
p -= GetElementProbability(input, Av1PartitionType.Split); p -= GetElementProbability(input, Av1PartitionType.Split);
@ -668,9 +1021,18 @@ internal ref struct Av1SymbolDecoder
return new(Av1Distribution.ProbabilityTop - p); return new(Av1Distribution.ProbabilityTop - p);
} }
/// <summary>
/// Collapses a full partition distribution into the split-versus-vertical boundary decision.
/// </summary>
/// <param name="inputs">The full partition distributions.</param>
/// <param name="blockSize">The current block size.</param>
/// <param name="context">The partition probability context.</param>
/// <returns>The binary cumulative distribution for split versus the vertical-like partition group.</returns>
internal static Av1Distribution GetSplitOrVerticalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) internal static Av1Distribution GetSplitOrVerticalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context)
{ {
Av1Distribution input = inputs[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; uint p = Av1Distribution.ProbabilityTop;
p -= GetElementProbability(input, Av1PartitionType.Vertical); p -= GetElementProbability(input, Av1PartitionType.Vertical);
p -= GetElementProbability(input, Av1PartitionType.Split); p -= GetElementProbability(input, Av1PartitionType.Split);
@ -685,6 +1047,12 @@ internal ref struct Av1SymbolDecoder
return new(Av1Distribution.ProbabilityTop - p); return new(Av1Distribution.ProbabilityTop - p);
} }
/// <summary>
/// Gets one symbol's probability mass from adjacent inverse-CDF thresholds.
/// </summary>
/// <param name="probability">The inverse cumulative distribution.</param>
/// <param name="element">The partition symbol.</param>
/// <returns>The symbol's probability mass.</returns>
private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element)
=> probability[(int)element - 1] - probability[(int)element]; => probability[(int)element - 1] - probability[(int)element];
} }

297
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs

@ -4,39 +4,148 @@
using System.Buffers; using System.Buffers;
using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; 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.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
/// <summary>
/// Encodes AV1 tile syntax elements and transform coefficients with tile-local adaptive distributions.
/// </summary>
internal class Av1SymbolEncoder : IDisposable internal class Av1SymbolEncoder : IDisposable
{ {
/// <summary>
/// The tile-adaptive intra-block-copy distribution.
/// </summary>
private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
/// <summary>
/// The tile-adaptive partition-type distributions.
/// </summary>
private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes;
/// <summary>
/// The tile-adaptive key-frame luma-mode distributions.
/// </summary>
private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode; private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode;
/// <summary>
/// The tile-adaptive chroma intra-mode distributions.
/// </summary>
private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode; private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode;
/// <summary>
/// The tile-adaptive transform-block skip distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][] transformBlockSkip; private readonly Av1Distribution[][] transformBlockSkip;
/// <summary>
/// The tile-adaptive end-of-block token distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][][] endOfBlockFlag; private readonly Av1Distribution[][][] endOfBlockFlag;
/// <summary>
/// The tile-adaptive coefficient base-range distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][][] coefficientsBaseRange; private readonly Av1Distribution[][][] coefficientsBaseRange;
/// <summary>
/// The tile-adaptive coefficient base-level distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][][] coefficientsBase; private readonly Av1Distribution[][][] coefficientsBase;
/// <summary>
/// The tile-adaptive final-nonzero coefficient distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][][] coefficientsBaseEndOfBlock; private readonly Av1Distribution[][][] coefficientsBaseEndOfBlock;
/// <summary>
/// The tile-adaptive filter-intra enable distributions.
/// </summary>
private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra; private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra;
/// <summary>
/// The tile-adaptive filter-intra mode distribution.
/// </summary>
private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode; private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode;
/// <summary>
/// The tile-adaptive absolute quantizer delta distribution.
/// </summary>
private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute; private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute;
/// <summary>
/// The tile-adaptive DC sign distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][] dcSign; private readonly Av1Distribution[][] dcSign;
/// <summary>
/// The tile-adaptive end-of-block extra-bit distributions selected for the frame base quantizer.
/// </summary>
private readonly Av1Distribution[][][] endOfBlockExtra; private readonly Av1Distribution[][][] endOfBlockExtra;
/// <summary>
/// The tile-adaptive intra transform-type distributions.
/// </summary>
private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform; private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform;
/// <summary>
/// The tile-adaptive spatial segment-identifier distributions.
/// </summary>
private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId; private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId;
/// <summary>
/// The tile-adaptive directional angle-delta distributions.
/// </summary>
private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta; private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta;
/// <summary>
/// The tile-adaptive transform-skip distributions.
/// </summary>
private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip;
/// <summary>
/// The tile-adaptive skip-mode distributions.
/// </summary>
private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode; private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode;
/// <summary>
/// The tile-adaptive joint chroma-from-luma sign distribution.
/// </summary>
private readonly Av1Distribution chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign; private readonly Av1Distribution chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign;
/// <summary>
/// The tile-adaptive chroma-from-luma alpha-magnitude distributions.
/// </summary>
private readonly Av1Distribution[] chromaFromLumaAlpha = Av1DefaultDistributions.ChromaFromLumaAlpha; private readonly Av1Distribution[] chromaFromLumaAlpha = Av1DefaultDistributions.ChromaFromLumaAlpha;
/// <summary>
/// Indicates whether the range writer has been disposed.
/// </summary>
private bool isDisposed; private bool isDisposed;
/// <summary>
/// The configuration providing output and coefficient-context memory.
/// </summary>
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary>
/// The range writer producing the current tile payload.
/// </summary>
private Av1SymbolWriter writer; private Av1SymbolWriter writer;
/// <summary>
/// The frame base quantizer used to select coefficient probability models.
/// </summary>
private readonly int baseQIndex; private readonly int baseQIndex;
/// <summary>
/// Initializes a new instance of the <see cref="Av1SymbolEncoder"/> class for one AV1 tile.
/// </summary>
/// <param name="configuration">The configuration providing output and temporary memory.</param>
/// <param name="initialSize">The initial output buffer size in bytes.</param>
/// <param name="qIndex">The frame base quantizer index.</param>
public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex) public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex)
{ {
this.transformBlockSkip = Av1DefaultDistributions.GetTransformBlockSkip(qIndex); this.transformBlockSkip = Av1DefaultDistributions.GetTransformBlockSkip(qIndex);
@ -51,18 +160,33 @@ internal class Av1SymbolEncoder : IDisposable
this.baseQIndex = qIndex; this.baseQIndex = qIndex;
} }
/// <summary>
/// Writes the frame-local intra-block-copy flag.
/// </summary>
/// <param name="value">Indicates whether intra-block copy is selected.</param>
public void WriteUseIntraBlockCopy(bool value) public void WriteUseIntraBlockCopy(bool value)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(value, this.tileIntraBlockCopy); w.WriteSymbol(value, this.tileIntraBlockCopy);
} }
/// <summary>
/// Writes a complete block partition type using the selected partition context.
/// </summary>
/// <param name="partitionType">The partition type to encode.</param>
/// <param name="context">The partition probability context.</param>
public void WritePartitionType(Av1PartitionType partitionType, int context) public void WritePartitionType(Av1PartitionType partitionType, int context)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol((int)partitionType, this.tilePartitionTypes[context]); w.WriteSymbol((int)partitionType, this.tilePartitionTypes[context]);
} }
/// <summary>
/// Writes the split-versus-horizontal boundary decision for a block clipped at the right tile edge.
/// </summary>
/// <param name="partitionType">The split or horizontal partition outcome.</param>
/// <param name="blockSize">The current block size.</param>
/// <param name="context">The partition probability context.</param>
public void WriteSplitOrHorizontal(Av1PartitionType partitionType, Av1BlockSize blockSize, int context) public void WriteSplitOrHorizontal(Av1PartitionType partitionType, Av1BlockSize blockSize, int context)
{ {
Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context); Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context);
@ -71,6 +195,12 @@ internal class Av1SymbolEncoder : IDisposable
w.WriteSymbol(value, distribution); w.WriteSymbol(value, distribution);
} }
/// <summary>
/// Writes the split-versus-vertical boundary decision for a block clipped at the bottom tile edge.
/// </summary>
/// <param name="partitionType">The split or vertical partition outcome.</param>
/// <param name="blockSize">The current block size.</param>
/// <param name="context">The partition probability context.</param>
public void WriteSplitOrVertical(Av1PartitionType partitionType, Av1BlockSize blockSize, int context) public void WriteSplitOrVertical(Av1PartitionType partitionType, Av1BlockSize blockSize, int context)
{ {
Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, context); Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, context);
@ -80,8 +210,18 @@ internal class Av1SymbolEncoder : IDisposable
} }
/// <summary> /// <summary>
/// SVT: av1_write_coeffs_txb_1d /// Encodes one transform block's coefficient syntax using scan-order probability contexts.
/// </summary> /// </summary>
/// <param name="transformSize">The signaled transform size.</param>
/// <param name="transformType">The transform type selecting the scan and context class.</param>
/// <param name="intraDirection">The block's intra prediction mode.</param>
/// <param name="coefficientBuffer">The raster-ordered signed coefficient levels.</param>
/// <param name="componentType">The luma or chroma component category.</param>
/// <param name="transformBlockContext">The neighboring skip and DC sign contexts.</param>
/// <param name="endOfBlock">The one-based final nonzero scan position, or zero for an empty block.</param>
/// <param name="useReducedTransformSet">Indicates whether the frame restricts transform choices.</param>
/// <param name="filterIntraMode">The selected filter-intra mode, or the disabled sentinel.</param>
/// <returns>The packed coefficient context used by adjacent transform blocks.</returns>
public int WriteCoefficients( public int WriteCoefficients(
Av1TransformSize transformSize, Av1TransformSize transformSize,
Av1TransformType transformType, Av1TransformType transformType,
@ -94,17 +234,18 @@ internal class Av1SymbolEncoder : IDisposable
Av1FilterIntraMode filterIntraMode) Av1FilterIntraMode filterIntraMode)
{ {
int c; int c;
int width = transformSize.GetWidth(); Av1TransformSize adjustedTransformSize = transformSize.GetAdjusted();
int height = transformSize.GetHeight(); int width = adjustedTransformSize.GetWidth();
int height = adjustedTransformSize.GetHeight();
Av1TransformClass transformClass = transformType.ToClass(); Av1TransformClass transformClass = transformType.ToClass();
Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType);
ReadOnlySpan<short> scan = scanOrder.Scan; ReadOnlySpan<short> scan = scanOrder.Scan;
int blockWidthLog2 = transformSize.GetBlockWidthLog2();
Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize); Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize);
ref Av1SymbolWriter w = ref this.writer; 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<sbyte> coefficientContexts = new sbyte[width * height]; Span<sbyte> coefficientContexts = new sbyte[width * height];
Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext)); Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext));
@ -145,7 +286,7 @@ internal class Av1SymbolEncoder : IDisposable
if (level > Av1Constants.BaseLevelsCount) 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 baseRange = level - 1 - Av1Constants.BaseLevelsCount;
int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, position, transformClass); int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, position, transformClass);
for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) 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, // Signs follow every magnitude so the DC sign can use its neighboring context and AC signs remain literals.
// starting with the sign of DC (if applicable) int culLevel = 0;
int cul_level = 0;
for (c = 0; c < endOfBlock; ++c) for (c = 0; c < endOfBlock; ++c)
{ {
short pos = scan[c]; short pos = scan[c];
int v = coefficientBuffer[pos]; int v = coefficientBuffer[pos];
int level = Math.Abs(v); int level = Math.Abs(v);
cul_level += level; culLevel += level;
uint sign = v < 0 ? 1u : 0u; uint sign = v < 0 ? 1u : 0u;
if (level > 0) 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 // The DC sign is packed above the magnitude bits so adjacent blocks can derive both contexts from one value.
Av1SymbolContextHelper.SetDcSign(ref cul_level, coefficientBuffer[0]); Av1SymbolContextHelper.SetDcSign(ref culLevel, coefficientBuffer[0]);
return cul_level; return culLevel;
} }
/// <summary>
/// Writes an end-of-block token and its context-coded and literal suffix bits.
/// </summary>
/// <param name="endOfBlock">The one-based final nonzero scan position.</param>
/// <param name="componentType">The luma or chroma component category.</param>
/// <param name="transformClass">The transform direction class.</param>
/// <param name="transformSize">The signaled transform size selecting the token alphabet.</param>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
internal void WriteEndOfBlockPosition(ushort endOfBlock, Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, Av1TransformSize transformSizeContext) internal void WriteEndOfBlockPosition(ushort endOfBlock, Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, Av1TransformSize transformSizeContext)
{ {
short endOfBlockPosition = Av1SymbolContextHelper.GetEndOfBlockPosition(endOfBlock, out int eobExtra); short endOfBlockPosition = Av1SymbolContextHelper.GetEndOfBlockPosition(endOfBlock, out int eobExtra);
@ -207,7 +355,8 @@ internal class Av1SymbolEncoder : IDisposable
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
int eobShift = eobOffsetBitCount - 1; int eobShift = eobOffsetBitCount - 1;
int bit = Av1Math.GetBit(eobExtra, eobShift); 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++) for (int i = 1; i < eobOffsetBitCount; i++)
{ {
eobShift = eobOffsetBitCount - 1 - i; eobShift = eobOffsetBitCount - 1 - i;
@ -217,18 +366,31 @@ internal class Av1SymbolEncoder : IDisposable
} }
} }
/// <summary>
/// Writes whether a transform block has no coded coefficients.
/// </summary>
/// <param name="skip">Indicates whether the transform block is empty.</param>
/// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="skipContext">The context derived from neighboring coefficient blocks.</param>
internal void WriteTransformBlockSkip(bool skip, Av1TransformSize transformSizeContext, int skipContext) internal void WriteTransformBlockSkip(bool skip, Av1TransformSize transformSizeContext, int skipContext)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(skip, this.transformBlockSkip[(int)transformSizeContext][skipContext]); w.WriteSymbol(skip, this.transformBlockSkip[(int)transformSizeContext][skipContext]);
} }
/// <summary>
/// Finalizes the range-coded tile payload and transfers ownership of its memory.
/// </summary>
/// <returns>The memory owner containing the encoded tile bytes.</returns>
public IMemoryOwner<byte> Exit() public IMemoryOwner<byte> Exit()
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
return w.Exit(); return w.Exit();
} }
/// <summary>
/// Releases output memory that has not been transferred by <see cref="Exit"/>.
/// </summary>
public void Dispose() public void Dispose()
{ {
if (!this.isDisposed) if (!this.isDisposed)
@ -239,8 +401,9 @@ internal class Av1SymbolEncoder : IDisposable
} }
/// <summary> /// <summary>
/// SVT: write_golomb /// Writes the unsigned exponential-Golomb suffix used for coefficient levels beyond the base range.
/// </summary> /// </summary>
/// <param name="level">The nonnegative suffix value.</param>
internal void WriteGolomb(int level) internal void WriteGolomb(int level)
{ {
uint x = (uint)level + 1u; uint x = (uint)level + 1u;
@ -260,6 +423,13 @@ internal class Av1SymbolEncoder : IDisposable
} }
} }
/// <summary>
/// Writes the end-of-block token for a transform coefficient-count category.
/// </summary>
/// <param name="componentType">The luma or chroma component category.</param>
/// <param name="transformClass">The transform direction class.</param>
/// <param name="transformSize">The signaled transform size.</param>
/// <param name="endOfBlockPosition">The one-based end-of-block token.</param>
private void WriteEndOfBlockFlag(Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, int endOfBlockPosition) private void WriteEndOfBlockFlag(Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, int endOfBlockPosition)
{ {
int endOfBlockMultiSize = transformSize.GetLog2Minus4(); int endOfBlockMultiSize = transformSize.GetLog2Minus4();
@ -269,8 +439,14 @@ internal class Av1SymbolEncoder : IDisposable
} }
/// <summary> /// <summary>
/// SVT: av1_write_tx_type /// Writes an intra transform type when the permitted transform set contains multiple choices.
/// </summary> /// </summary>
/// <param name="transformType">The transform type to encode.</param>
/// <param name="transformSize">The signaled transform size.</param>
/// <param name="useReducedTransformSet">Indicates whether the frame restricts transform choices.</param>
/// <param name="baseQIndex">The active base quantizer index.</param>
/// <param name="filterIntraMode">The filter-intra mode when enabled.</param>
/// <param name="intraDirection">The ordinary intra prediction mode.</param>
internal void WriteTransformType( internal void WriteTransformType(
Av1TransformType transformType, Av1TransformType transformType,
Av1TransformSize transformSize, Av1TransformSize transformSize,
@ -279,7 +455,7 @@ internal class Av1SymbolEncoder : IDisposable
Av1FilterIntraMode filterIntraMode, Av1FilterIntraMode filterIntraMode,
Av1PredictionMode intraDirection) 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); Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet);
if (Av1SymbolContextHelper.GetExtendedTransformTypeCount(transformSetType) > 1 && baseQIndex > 0) if (Av1SymbolContextHelper.GetExtendedTransformTypeCount(transformSetType) > 1 && baseQIndex > 0)
{ {
@ -288,11 +464,9 @@ internal class Av1SymbolEncoder : IDisposable
int extendedSet = Av1SymbolContextHelper.GetExtendedTransformSet(transformSetType); int extendedSet = Av1SymbolContextHelper.GetExtendedTransformSet(transformSetType);
// eset == 0 should correspond to a set with only DCT_DCT and there // Set zero contains only DCT-DCT, which was excluded by the multiple-choice condition above.
// is no need to send the tx_type
Guard.MustBeGreaterThan(extendedSet, 0, nameof(extendedSet)); Guard.MustBeGreaterThan(extendedSet, 0, nameof(extendedSet));
// assert(av1_ext_tx_used[tx_set_type][transformType]);
Av1PredictionMode intraDirectionContext; Av1PredictionMode intraDirectionContext;
if (filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes) if (filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes)
{ {
@ -312,24 +486,44 @@ internal class Av1SymbolEncoder : IDisposable
} }
} }
/// <summary>
/// Writes a spatially predicted segment identifier.
/// </summary>
/// <param name="segmentId">The segment identifier.</param>
/// <param name="context">The context derived from neighboring segment identifiers.</param>
internal void WriteSegmentId(int segmentId, int context) internal void WriteSegmentId(int segmentId, int context)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(segmentId, this.segmentId[context]); w.WriteSymbol(segmentId, this.segmentId[context]);
} }
/// <summary>
/// Writes the transform-skip flag from a neighboring skip context.
/// </summary>
/// <param name="skip">Indicates whether the block contains no coded transform coefficients.</param>
/// <param name="context">The neighboring skip context.</param>
internal void WriteSkip(bool skip, int context) internal void WriteSkip(bool skip, int context)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(skip, this.skip[context]); w.WriteSymbol(skip, this.skip[context]);
} }
/// <summary>
/// Writes the compound-reference skip-mode flag.
/// </summary>
/// <param name="skip">Indicates whether skip mode is selected.</param>
/// <param name="context">The neighboring skip-mode context.</param>
internal void WriteSkipMode(bool skip, int context) internal void WriteSkipMode(bool skip, int context)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(skip, this.skipMode[context]); w.WriteSymbol(skip, this.skipMode[context]);
} }
/// <summary>
/// Writes the filter-intra enable flag and, when enabled, its prediction mode.
/// </summary>
/// <param name="filterIntraMode">The selected filter-intra mode, or the disabled sentinel.</param>
/// <param name="blockSize">The block size selecting the enable distribution.</param>
internal void WriteFilterIntraMode(Av1FilterIntraMode filterIntraMode, Av1BlockSize blockSize) internal void WriteFilterIntraMode(Av1FilterIntraMode filterIntraMode, Av1BlockSize blockSize)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
@ -342,23 +536,25 @@ internal class Av1SymbolEncoder : IDisposable
} }
/// <summary> /// <summary>
/// SVT: av1_write_delta_q_index /// Writes a signed quantizer-index delta value.
/// </summary> /// </summary>
/// <param name="deltaQindex">The signed quantizer-index delta.</param>
internal void WriteDeltaQuantizerIndex(int deltaQindex) internal void WriteDeltaQuantizerIndex(int deltaQindex)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
bool sign = deltaQindex < 0; bool sign = deltaQindex < 0;
int abs = Math.Abs(deltaQindex); int abs = Math.Abs(deltaQindex);
bool smallval = abs < Av1Constants.DeltaQuantizerSmall; bool isSmallValue = abs < Av1Constants.DeltaQuantizerSmall;
w.WriteSymbol(Math.Min(abs, Av1Constants.DeltaQuantizerSmall), this.deltaQuantizerAbsolute); w.WriteSymbol(Math.Min(abs, Av1Constants.DeltaQuantizerSmall), this.deltaQuantizerAbsolute);
if (!smallval) if (!isSmallValue)
{ {
int rem_bits = Av1Math.MostSignificantBit((uint)(abs - 1)); // Escape magnitudes encode their bit width first, followed by the offset within that width's range.
int threshold = (1 << rem_bits) + 1; int remainingBitCount = Av1Math.MostSignificantBit((uint)(abs - 1));
w.WriteLiteral((uint)(rem_bits - 1), 3); int threshold = (1 << remainingBitCount) + 1;
w.WriteLiteral((uint)(abs - threshold), rem_bits); w.WriteLiteral((uint)(remainingBitCount - 1), 3);
w.WriteLiteral((uint)(abs - threshold), remainingBitCount);
} }
if (abs > 0) if (abs > 0)
@ -367,24 +563,46 @@ internal class Av1SymbolEncoder : IDisposable
} }
} }
/// <summary>
/// Writes a key-frame luma prediction mode using the above and left mode contexts.
/// </summary>
/// <param name="lumaMode">The luma prediction mode.</param>
/// <param name="topContext">The reduced above-mode context.</param>
/// <param name="leftContext">The reduced left-mode context.</param>
internal void WriteLumaMode(Av1PredictionMode lumaMode, byte topContext, byte leftContext) internal void WriteLumaMode(Av1PredictionMode lumaMode, byte topContext, byte leftContext)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol((int)lumaMode, this.keyFrameYMode[topContext][leftContext]); w.WriteSymbol((int)lumaMode, this.keyFrameYMode[topContext][leftContext]);
} }
/// <summary>
/// Writes an unsigned directional angle-delta symbol.
/// </summary>
/// <param name="angleDelta">The signed angle delta offset by <see cref="Av1Constants.MaxAngleDelta"/>.</param>
/// <param name="context">The directional prediction mode selecting the distribution.</param>
internal void WriteAngleDelta(int angleDelta, Av1PredictionMode context) internal void WriteAngleDelta(int angleDelta, Av1PredictionMode context)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(angleDelta, this.angleDelta[context - Av1PredictionMode.Vertical]); w.WriteSymbol(angleDelta, this.angleDelta[context - Av1PredictionMode.Vertical]);
} }
/// <summary>
/// Writes a fixed-width CDEF strength index.
/// </summary>
/// <param name="cdefStrength">The CDEF strength index.</param>
/// <param name="bitCount">The number of signaled bits.</param>
internal void WriteCdefStrength(int cdefStrength, int bitCount) internal void WriteCdefStrength(int cdefStrength, int bitCount)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
w.WriteLiteral((uint)cdefStrength, bitCount); w.WriteLiteral((uint)cdefStrength, bitCount);
} }
/// <summary>
/// Writes a chroma intra prediction mode conditioned on the luma mode and chroma-from-luma availability.
/// </summary>
/// <param name="chromaMode">The chroma prediction mode.</param>
/// <param name="isChromaFromLumaAllowed">Indicates whether chroma-from-luma is valid for the block.</param>
/// <param name="lumaMode">The block's luma prediction mode.</param>
internal void WriteChromaMode(Av1PredictionMode chromaMode, bool isChromaFromLumaAllowed, Av1PredictionMode lumaMode) internal void WriteChromaMode(Av1PredictionMode chromaMode, bool isChromaFromLumaAllowed, Av1PredictionMode lumaMode)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
@ -392,25 +610,30 @@ internal class Av1SymbolEncoder : IDisposable
w.WriteSymbol((int)chromaMode, this.uvMode[cflAllowed][(int)lumaMode]); w.WriteSymbol((int)chromaMode, this.uvMode[cflAllowed][(int)lumaMode]);
} }
/// <summary>
/// Writes the joint chroma-from-luma signs and the magnitude index for each nonzero plane.
/// </summary>
/// <param name="chromaFromLumaIndex">The packed U/V alpha-magnitude indices.</param>
/// <param name="joinedSign">The joint U/V sign symbol.</param>
internal void WriteChromaFromLumaAlphas(int chromaFromLumaIndex, int joinedSign) internal void WriteChromaFromLumaAlphas(int chromaFromLumaIndex, int joinedSign)
{ {
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(joinedSign, this.chromaFromLumaSign); w.WriteSymbol(joinedSign, this.chromaFromLumaSign);
// Magnitudes are only signaled for nonzero codes. // Magnitudes are only signaled for nonzero signs; the shared helper keeps encoder and decoder mappings exact.
int signU = ((joinedSign + 1) * 11) >> 5; int signU = Av1ChromaFromLumaMath.SignU(joinedSign);
if (signU != 0) if (signU != Av1ChromaFromLumaMath.SignZero)
{ {
int contextU = chromaFromLumaIndex - 2; int contextU = Av1ChromaFromLumaMath.ContextU(joinedSign);
int indexU = chromaFromLumaIndex >> Av1Constants.ChromaFromLumaAlphabetSizeLog2; int indexU = Av1ChromaFromLumaMath.IndexU(chromaFromLumaIndex);
w.WriteSymbol(indexU, this.chromaFromLumaAlpha[contextU]); w.WriteSymbol(indexU, this.chromaFromLumaAlpha[contextU]);
} }
int signV = (joinedSign + 1) - (3 * signU); int signV = Av1ChromaFromLumaMath.SignV(joinedSign);
if (signV != 0) if (signV != Av1ChromaFromLumaMath.SignZero)
{ {
int contextV = (signV * 3) - signU - 3; int contextV = Av1ChromaFromLumaMath.ContextV(joinedSign);
int indexV = chromaFromLumaIndex & ((1 << Av1Constants.ChromaFromLumaAlphabetSizeLog2) - 1); int indexV = Av1ChromaFromLumaMath.IndexV(chromaFromLumaIndex);
w.WriteSymbol(indexV, this.chromaFromLumaAlpha[contextV]); w.WriteSymbol(indexV, this.chromaFromLumaAlpha[contextV]);
} }
} }

Loading…
Cancel
Save