diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs
index d962e5a605..0d618019bd 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs
@@ -34,9 +34,9 @@ internal sealed class Av1Distribution
private const int ProbabilityBitCount = 15;
///
- /// The inverse cumulative thresholds followed by the required zero sentinel.
+ /// The Q15 inverse cumulative thresholds followed by the required zero sentinel.
///
- private InlineArray16 probabilities;
+ private InlineArray16 probabilities;
///
/// The symbol-count contribution to the adaptive update rate.
@@ -273,13 +273,13 @@ internal sealed class Av1Distribution
/// The symbol-count contribution to the update rate.
private Av1Distribution(ReadOnlySpan props, int speed)
{
- Span probabilities = this.probabilities;
+ Span probabilities = this.probabilities;
- // AV1 range coding consumes inverse cumulative thresholds. The defaults are written in the more readable
- // forward form, so convert every real threshold while leaving the final zero sentinel untouched.
+ // Q15 thresholds range from zero through 32768, including zero-mass intervals, and fit in unsigned 16-bit storage.
+ // Convert the forward defaults before narrowing; the final zero sentinel is already initialized.
for (int i = 0; i < props.Length - 1; i++)
{
- probabilities[i] = ProbabilityTop - props[i];
+ probabilities[i] = (ushort)(ProbabilityTop - props[i]);
}
this.NumberOfSymbols = props.Length;
@@ -292,8 +292,8 @@ internal sealed class Av1Distribution
/// The distribution state to copy.
private Av1Distribution(Av1Distribution source)
{
- ReadOnlySpan sourceProbabilities = source.probabilities;
- Span probabilities = this.probabilities;
+ ReadOnlySpan sourceProbabilities = source.probabilities;
+ Span probabilities = this.probabilities;
sourceProbabilities[..source.NumberOfSymbols].CopyTo(probabilities);
// 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
// working tile never allocates or replaces the distribution objects referenced by the symbol decoder.
- ReadOnlySpan sourceProbabilities = source.probabilities;
- Span probabilities = this.probabilities;
+ ReadOnlySpan sourceProbabilities = source.probabilities;
+ Span probabilities = this.probabilities;
sourceProbabilities[..source.NumberOfSymbols].CopyTo(probabilities);
this.updateCount = source.updateCount;
}
@@ -402,7 +402,8 @@ internal sealed class Av1Distribution
int tmp = ProbabilityTop;
// 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++)
{
tmp = i == value ? 0 : tmp;