Browse Source

Store AV1 cumulative probabilities in 16 bits

pull/2633/head
James Jackson-South 4 weeks ago
parent
commit
a658a2cb7c
  1. 23
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs

23
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs

@ -34,9 +34,9 @@ internal sealed class Av1Distribution
private const int ProbabilityBitCount = 15; private const int ProbabilityBitCount = 15;
/// <summary> /// <summary>
/// The inverse cumulative thresholds followed by the required zero sentinel. /// The Q15 inverse cumulative thresholds followed by the required zero sentinel.
/// </summary> /// </summary>
private InlineArray16<uint> probabilities; private InlineArray16<ushort> probabilities;
/// <summary> /// <summary>
/// The symbol-count contribution to the adaptive update rate. /// The symbol-count contribution to the adaptive update rate.
@ -273,13 +273,13 @@ internal sealed class Av1Distribution
/// <param name="speed">The symbol-count contribution to the update rate.</param> /// <param name="speed">The symbol-count contribution to the update rate.</param>
private Av1Distribution(ReadOnlySpan<uint> props, int speed) private Av1Distribution(ReadOnlySpan<uint> props, int speed)
{ {
Span<uint> probabilities = this.probabilities; Span<ushort> probabilities = this.probabilities;
// AV1 range coding consumes inverse cumulative thresholds. The defaults are written in the more readable // Q15 thresholds range from zero through 32768, including zero-mass intervals, and fit in unsigned 16-bit storage.
// forward form, so convert every real threshold while leaving the final zero sentinel untouched. // Convert the forward defaults before narrowing; the final zero sentinel is already initialized.
for (int i = 0; i < props.Length - 1; i++) for (int i = 0; i < props.Length - 1; i++)
{ {
probabilities[i] = ProbabilityTop - props[i]; probabilities[i] = (ushort)(ProbabilityTop - props[i]);
} }
this.NumberOfSymbols = props.Length; this.NumberOfSymbols = props.Length;
@ -292,8 +292,8 @@ internal sealed class Av1Distribution
/// <param name="source">The distribution state to copy.</param> /// <param name="source">The distribution state to copy.</param>
private Av1Distribution(Av1Distribution source) private Av1Distribution(Av1Distribution source)
{ {
ReadOnlySpan<uint> sourceProbabilities = source.probabilities; ReadOnlySpan<ushort> sourceProbabilities = source.probabilities;
Span<uint> probabilities = this.probabilities; Span<ushort> probabilities = this.probabilities;
sourceProbabilities[..source.NumberOfSymbols].CopyTo(probabilities); sourceProbabilities[..source.NumberOfSymbols].CopyTo(probabilities);
// The adaptation rate depends on both the alphabet size and prior update count, so copying only the // The adaptation rate depends on both the alphabet size and prior update count, so copying only the
@ -329,8 +329,8 @@ internal sealed class Av1Distribution
{ {
// Entropy contexts are created from the same fixed default table shape. Copy only mutable state so resetting a // Entropy contexts are created from the same fixed default table shape. Copy only mutable state so resetting a
// working tile never allocates or replaces the distribution objects referenced by the symbol decoder. // working tile never allocates or replaces the distribution objects referenced by the symbol decoder.
ReadOnlySpan<uint> sourceProbabilities = source.probabilities; ReadOnlySpan<ushort> sourceProbabilities = source.probabilities;
Span<uint> probabilities = this.probabilities; Span<ushort> probabilities = this.probabilities;
sourceProbabilities[..source.NumberOfSymbols].CopyTo(probabilities); sourceProbabilities[..source.NumberOfSymbols].CopyTo(probabilities);
this.updateCount = source.updateCount; this.updateCount = source.updateCount;
} }
@ -402,7 +402,8 @@ internal sealed class Av1Distribution
int tmp = ProbabilityTop; int tmp = ProbabilityTop;
// Switching tmp to zero at the observed symbol moves the thresholds on either side toward the sample while // Switching tmp to zero at the observed symbol moves the thresholds on either side toward the sample while
// preserving their inverse-cumulative ordering in one pass. // preserving their inverse-cumulative ordering in one pass. Arithmetic stays wide until the stored update;
// each step moves toward zero or 32768, so narrowing cannot discard a significant probability bit.
for (int i = 0; i < this.NumberOfSymbols - 1; i++) for (int i = 0; i < this.NumberOfSymbols - 1; i++)
{ {
tmp = i == value ? 0 : tmp; tmp = i == value ? 0 : tmp;

Loading…
Cancel
Save