diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs
index c645520d02..278e2bd200 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs
@@ -267,6 +267,22 @@ internal class Av1Distribution
this.speed = speed;
}
+ ///
+ /// Initializes a new instance of the class with the same probability and adaptation state as another distribution.
+ ///
+ /// The distribution state to copy.
+ private Av1Distribution(Av1Distribution source)
+ {
+ this.probabilities = new uint[source.probabilities.Length];
+ source.probabilities.CopyTo(this.probabilities, 0);
+
+ // The adaptation rate depends on both the alphabet size and prior update count, so copying only the
+ // thresholds would make the cloned frame context diverge after its next symbol.
+ this.speed = source.speed;
+ this.updateCount = source.updateCount;
+ this.NumberOfSymbols = source.NumberOfSymbols;
+ }
+
///
/// Gets the number of symbols represented by the distribution.
///
@@ -279,6 +295,60 @@ internal class Av1Distribution
/// The Q15 inverse cumulative threshold.
public uint this[int index] => this.probabilities[index];
+ ///
+ /// Creates an independently adaptable copy of a distribution.
+ ///
+ /// A distribution initialized with the same probabilities and update count.
+ public Av1Distribution CreateCopy() => new(this);
+
+ ///
+ /// Creates independently adaptable copies of a distribution array.
+ ///
+ /// The distributions to copy.
+ /// An array with the same shape and distribution state.
+ public static Av1Distribution[] CreateCopy(Av1Distribution[] source)
+ {
+ Av1Distribution[] result = new Av1Distribution[source.Length];
+ for (int i = 0; i < source.Length; i++)
+ {
+ result[i] = source[i].CreateCopy();
+ }
+
+ return result;
+ }
+
+ ///
+ /// Creates independently adaptable copies of a two-dimensional jagged distribution array.
+ ///
+ /// The distributions to copy.
+ /// An array with the same shape and distribution state.
+ public static Av1Distribution[][] CreateCopy(Av1Distribution[][] source)
+ {
+ Av1Distribution[][] result = new Av1Distribution[source.Length][];
+ for (int i = 0; i < source.Length; i++)
+ {
+ result[i] = CreateCopy(source[i]);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Creates independently adaptable copies of a three-dimensional jagged distribution array.
+ ///
+ /// The distributions to copy.
+ /// An array with the same shape and distribution state.
+ public static Av1Distribution[][][] CreateCopy(Av1Distribution[][][] source)
+ {
+ Av1Distribution[][][] result = new Av1Distribution[source.Length][][];
+ for (int i = 0; i < source.Length; i++)
+ {
+ result[i] = CreateCopy(source[i]);
+ }
+
+ return result;
+ }
+
///
/// Adapts the cumulative thresholds after coding one symbol.
///
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
index 73d56bdebb..44726c7a12 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
@@ -21,67 +21,67 @@ internal ref struct Av1SymbolDecoder
///
/// The tile-adaptive intra-block-copy distribution.
///
- private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
+ private readonly Av1Distribution tileIntraBlockCopy;
///
/// The tile-adaptive partition-type distributions.
///
- private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes;
+ private readonly Av1Distribution[] tilePartitionTypes;
///
/// The tile-adaptive key-frame luma-mode distributions.
///
- private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode;
+ private readonly Av1Distribution[][] keyFrameYMode;
///
/// The tile-adaptive chroma intra-mode distributions.
///
- private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode;
+ private readonly Av1Distribution[][] uvMode;
///
/// The tile-adaptive transform-skip distributions.
///
- private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip;
+ private readonly Av1Distribution[] skip;
///
/// The tile-adaptive skip-mode distributions.
///
- private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode;
+ private readonly Av1Distribution[] skipMode;
///
/// The tile-adaptive absolute loop-filter delta distribution.
///
- private readonly Av1Distribution deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute;
+ private readonly Av1Distribution deltaLoopFilterAbsolute;
///
/// The tile-adaptive absolute quantizer delta distribution.
///
- private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute;
+ private readonly Av1Distribution deltaQuantizerAbsolute;
///
/// The tile-adaptive spatial segment-identifier distributions.
///
- private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId;
+ private readonly Av1Distribution[] segmentId;
///
/// The tile-adaptive directional angle-delta distributions.
///
- private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta;
+ private readonly Av1Distribution[] angleDelta;
///
/// The tile-adaptive filter-intra mode distribution.
///
- private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode;
+ private readonly Av1Distribution filterIntraMode;
///
/// The tile-adaptive filter-intra enable distributions.
///
- private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra;
+ private readonly Av1Distribution[] filterIntra;
///
/// The tile-adaptive transform-size distributions.
///
- private readonly Av1Distribution[][] transformSize = Av1DefaultDistributions.TransformSize;
+ private readonly Av1Distribution[][] transformSize;
///
/// The tile-adaptive end-of-block token distributions selected for the frame base quantizer.
@@ -121,17 +121,17 @@ internal ref struct Av1SymbolDecoder
///
/// The tile-adaptive joint chroma-from-luma sign distribution.
///
- private readonly Av1Distribution chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign;
+ private readonly Av1Distribution chromaFromLumaSign;
///
/// The tile-adaptive chroma-from-luma alpha-magnitude distributions.
///
- private readonly Av1Distribution[] chromaFromLumaAlpha = Av1DefaultDistributions.ChromaFromLumaAlpha;
+ private readonly Av1Distribution[] chromaFromLumaAlpha;
///
/// The tile-adaptive intra transform-type distributions.
///
- private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform;
+ private readonly Av1Distribution[][][] intraExtendedTransform;
///
/// The configuration providing temporary coefficient-context memory.
@@ -154,18 +154,37 @@ internal ref struct Av1SymbolDecoder
/// The configuration providing temporary memory.
/// The entropy-coded tile payload.
/// The frame base quantizer index.
- public Av1SymbolDecoder(Configuration configuration, Span tileData, int qIndex)
+ /// A value indicating whether decoded symbols adapt their tile distributions.
+ public Av1SymbolDecoder(Configuration configuration, Span tileData, int qIndex, bool updateCdf = true)
{
+ // Every tile starts from its own frame-context copy. Sharing these objects would let one image's adaptive
+ // updates change the initial probabilities used to decode the next tile or image.
+ this.tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy.CreateCopy();
+ this.tilePartitionTypes = Av1Distribution.CreateCopy(Av1DefaultDistributions.PartitionTypes);
+ this.keyFrameYMode = Av1Distribution.CreateCopy(Av1DefaultDistributions.KeyFrameYMode);
+ this.uvMode = Av1Distribution.CreateCopy(Av1DefaultDistributions.UvMode);
+ this.skip = Av1Distribution.CreateCopy(Av1DefaultDistributions.Skip);
+ this.skipMode = Av1Distribution.CreateCopy(Av1DefaultDistributions.SkipMode);
+ this.deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute.CreateCopy();
+ this.deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute.CreateCopy();
+ this.segmentId = Av1Distribution.CreateCopy(Av1DefaultDistributions.SegmentId);
+ this.angleDelta = Av1Distribution.CreateCopy(Av1DefaultDistributions.AngleDelta);
+ this.filterIntraMode = Av1DefaultDistributions.FilterIntraMode.CreateCopy();
+ this.filterIntra = Av1Distribution.CreateCopy(Av1DefaultDistributions.FilterIntra);
+ this.transformSize = Av1Distribution.CreateCopy(Av1DefaultDistributions.TransformSize);
+ this.chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign.CreateCopy();
+ this.chromaFromLumaAlpha = Av1Distribution.CreateCopy(Av1DefaultDistributions.ChromaFromLumaAlpha);
+ this.intraExtendedTransform = Av1Distribution.CreateCopy(Av1DefaultDistributions.IntraExtendedTransform);
this.configuration = configuration;
- this.reader = new Av1SymbolReader(tileData);
+ this.reader = new Av1SymbolReader(tileData, updateCdf);
this.baseQIndex = qIndex;
- this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex);
- this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex);
- this.baseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex);
- this.dcSign = Av1DefaultDistributions.GetDcSign(qIndex);
- this.coefficientsBaseRange = Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex);
- this.transformBlockSkip = Av1DefaultDistributions.GetTransformBlockSkip(qIndex);
- this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex);
+ this.endOfBlockFlag = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetEndOfBlockFlag(qIndex));
+ this.coefficientsBase = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetCoefficientsBase(qIndex));
+ this.baseEndOfBlock = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetBaseEndOfBlock(qIndex));
+ this.dcSign = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetDcSign(qIndex));
+ this.coefficientsBaseRange = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex));
+ this.transformBlockSkip = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetTransformBlockSkip(qIndex));
+ this.endOfBlockExtra = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetEndOfBlockExtra(qIndex));
}
///
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
index 57ea924ddc..666ae3d788 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
@@ -18,22 +18,22 @@ internal class Av1SymbolEncoder : IDisposable
///
/// The tile-adaptive intra-block-copy distribution.
///
- private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
+ private readonly Av1Distribution tileIntraBlockCopy;
///
/// The tile-adaptive partition-type distributions.
///
- private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes;
+ private readonly Av1Distribution[] tilePartitionTypes;
///
/// The tile-adaptive key-frame luma-mode distributions.
///
- private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode;
+ private readonly Av1Distribution[][] keyFrameYMode;
///
/// The tile-adaptive chroma intra-mode distributions.
///
- private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode;
+ private readonly Av1Distribution[][] uvMode;
///
/// The tile-adaptive transform-block skip distributions selected for the frame base quantizer.
@@ -63,17 +63,17 @@ internal class Av1SymbolEncoder : IDisposable
///
/// The tile-adaptive filter-intra enable distributions.
///
- private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra;
+ private readonly Av1Distribution[] filterIntra;
///
/// The tile-adaptive filter-intra mode distribution.
///
- private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode;
+ private readonly Av1Distribution filterIntraMode;
///
/// The tile-adaptive absolute quantizer delta distribution.
///
- private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute;
+ private readonly Av1Distribution deltaQuantizerAbsolute;
///
/// The tile-adaptive DC sign distributions selected for the frame base quantizer.
@@ -88,37 +88,37 @@ internal class Av1SymbolEncoder : IDisposable
///
/// The tile-adaptive intra transform-type distributions.
///
- private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform;
+ private readonly Av1Distribution[][][] intraExtendedTransform;
///
/// The tile-adaptive spatial segment-identifier distributions.
///
- private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId;
+ private readonly Av1Distribution[] segmentId;
///
/// The tile-adaptive directional angle-delta distributions.
///
- private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta;
+ private readonly Av1Distribution[] angleDelta;
///
/// The tile-adaptive transform-skip distributions.
///
- private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip;
+ private readonly Av1Distribution[] skip;
///
/// The tile-adaptive skip-mode distributions.
///
- private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode;
+ private readonly Av1Distribution[] skipMode;
///
/// The tile-adaptive joint chroma-from-luma sign distribution.
///
- private readonly Av1Distribution chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign;
+ private readonly Av1Distribution chromaFromLumaSign;
///
/// The tile-adaptive chroma-from-luma alpha-magnitude distributions.
///
- private readonly Av1Distribution[] chromaFromLumaAlpha = Av1DefaultDistributions.ChromaFromLumaAlpha;
+ private readonly Av1Distribution[] chromaFromLumaAlpha;
///
/// Indicates whether the range writer has been disposed.
@@ -146,17 +146,34 @@ internal class Av1SymbolEncoder : IDisposable
/// The configuration providing output and temporary memory.
/// The initial output buffer size in bytes.
/// The frame base quantizer index.
- public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex)
+ /// A value indicating whether encoded symbols adapt their tile distributions.
+ public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex, bool updateCdf = true)
{
- this.transformBlockSkip = Av1DefaultDistributions.GetTransformBlockSkip(qIndex);
- this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex);
- this.coefficientsBaseRange = Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex);
- this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex);
- this.coefficientsBaseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex);
- this.dcSign = Av1DefaultDistributions.GetDcSign(qIndex);
- this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex);
+ // Encoding and decoding must begin from equivalent tile-local models. Copying the defaults also prevents
+ // one encoded image from changing the probabilities used by later encoder or decoder instances.
+ this.tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy.CreateCopy();
+ this.tilePartitionTypes = Av1Distribution.CreateCopy(Av1DefaultDistributions.PartitionTypes);
+ this.keyFrameYMode = Av1Distribution.CreateCopy(Av1DefaultDistributions.KeyFrameYMode);
+ this.uvMode = Av1Distribution.CreateCopy(Av1DefaultDistributions.UvMode);
+ this.filterIntra = Av1Distribution.CreateCopy(Av1DefaultDistributions.FilterIntra);
+ this.filterIntraMode = Av1DefaultDistributions.FilterIntraMode.CreateCopy();
+ this.deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute.CreateCopy();
+ this.intraExtendedTransform = Av1Distribution.CreateCopy(Av1DefaultDistributions.IntraExtendedTransform);
+ this.segmentId = Av1Distribution.CreateCopy(Av1DefaultDistributions.SegmentId);
+ this.angleDelta = Av1Distribution.CreateCopy(Av1DefaultDistributions.AngleDelta);
+ this.skip = Av1Distribution.CreateCopy(Av1DefaultDistributions.Skip);
+ this.skipMode = Av1Distribution.CreateCopy(Av1DefaultDistributions.SkipMode);
+ this.chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign.CreateCopy();
+ this.chromaFromLumaAlpha = Av1Distribution.CreateCopy(Av1DefaultDistributions.ChromaFromLumaAlpha);
+ this.transformBlockSkip = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetTransformBlockSkip(qIndex));
+ this.endOfBlockFlag = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetEndOfBlockFlag(qIndex));
+ this.coefficientsBaseRange = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex));
+ this.coefficientsBase = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetCoefficientsBase(qIndex));
+ this.coefficientsBaseEndOfBlock = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetBaseEndOfBlock(qIndex));
+ this.dcSign = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetDcSign(qIndex));
+ this.endOfBlockExtra = Av1Distribution.CreateCopy(Av1DefaultDistributions.GetEndOfBlockExtra(qIndex));
this.configuration = configuration;
- this.writer = new(configuration, initialSize);
+ this.writer = new(configuration, initialSize, updateCdf);
this.baseQIndex = qIndex;
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolReader.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolReader.cs
index 19e4284575..d690c57193 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolReader.cs
@@ -23,6 +23,11 @@ internal ref struct Av1SymbolReader
///
private readonly Span buffer;
+ ///
+ /// Indicates whether decoded symbols adapt their distributions.
+ ///
+ private readonly bool updateCdf;
+
///
/// The next byte position to load into the code-value window.
///
@@ -51,9 +56,11 @@ internal ref struct Av1SymbolReader
/// Initializes a new instance of the struct over one entropy-coded span.
///
/// The bounded entropy-coded bytes.
- public Av1SymbolReader(Span span)
+ /// A value indicating whether decoded symbols adapt their distributions.
+ public Av1SymbolReader(Span span, bool updateCdf = true)
{
this.buffer = span;
+ this.updateCdf = updateCdf;
this.position = 0;
this.difference = (1U << (DecoderWindowsSize - 1)) - 1;
this.range = 0x8000;
@@ -62,7 +69,7 @@ internal ref struct Av1SymbolReader
}
///
- /// Reads one symbol and adapts its distribution.
+ /// Reads one symbol and adapts its distribution when CDF updates are enabled.
///
/// The inverse cumulative distribution for the symbol alphabet.
/// The decoded zero-based symbol.
@@ -70,8 +77,12 @@ internal ref struct Av1SymbolReader
{
int value = this.DecodeIntegerQ15(distribution);
- // Decoder and encoder must adapt after the same symbol so their subsequent intervals remain identical.
- distribution.Update(value);
+ // disable_cdf_update freezes every tile distribution while leaving range decoding unchanged.
+ if (this.updateCdf)
+ {
+ distribution.Update(value);
+ }
+
return value;
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs
index 913228cf1e..6da6b857bf 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs
@@ -39,6 +39,11 @@ internal class Av1SymbolWriter : IDisposable
///
private readonly AutoExpandingMemory memory;
+ ///
+ /// Indicates whether encoded symbols adapt their distributions.
+ ///
+ private readonly bool updateCdf;
+
///
/// The next pre-carry output position.
///
@@ -49,10 +54,12 @@ internal class Av1SymbolWriter : IDisposable
///
/// The configuration that supplies output allocation.
/// The estimated encoded size in bytes.
- public Av1SymbolWriter(Configuration configuration, int initialSize)
+ /// A value indicating whether encoded symbols adapt their distributions.
+ public Av1SymbolWriter(Configuration configuration, int initialSize, bool updateCdf = true)
{
this.configuration = configuration;
this.memory = new AutoExpandingMemory(configuration, (initialSize + 1) >> 1);
+ this.updateCdf = updateCdf;
}
///
@@ -61,7 +68,7 @@ internal class Av1SymbolWriter : IDisposable
public void Dispose() => this.memory.Dispose();
///
- /// Writes one binary symbol and adapts its distribution.
+ /// Writes one binary symbol and adapts its distribution when CDF updates are enabled.
///
/// The binary symbol.
/// The inverse cumulative distribution for the binary alphabet.
@@ -69,7 +76,7 @@ internal class Av1SymbolWriter : IDisposable
=> this.WriteSymbol(symbol ? 1 : 0, distribution);
///
- /// Writes one symbol and adapts its distribution.
+ /// Writes one symbol and adapts its distribution when CDF updates are enabled.
///
/// The zero-based symbol.
/// The inverse cumulative distribution for the symbol alphabet.
@@ -80,7 +87,12 @@ internal class Av1SymbolWriter : IDisposable
DebugGuard.IsTrue(distribution[distribution.NumberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero.");
this.EncodeIntegerQ15(symbol, distribution);
- distribution.Update(symbol);
+
+ // disable_cdf_update freezes every tile distribution while leaving range encoding unchanged.
+ if (this.updateCdf)
+ {
+ distribution.Update(symbol);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
index d8a3fd1e37..a45f20264d 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
@@ -168,7 +168,14 @@ internal class Av1TileReader : IAv1TileReader
/// Corresponds to parse_tile in SVT-AV1.
public void ReadTile(Span tileData, int tileNum)
{
- Av1SymbolDecoder reader = new(this.configuration, tileData, this.FrameHeader.QuantizationParameters.BaseQIndex);
+ // The frame syntax exposes a disable flag, while the range reader follows libaom's positive
+ // allow_update_cdf convention.
+ Av1SymbolDecoder reader = new(
+ this.configuration,
+ tileData,
+ this.FrameHeader.QuantizationParameters.BaseQIndex,
+ !this.FrameHeader.DisableCdfUpdate);
+
int tileColumnIndex = tileNum % this.FrameHeader.TilesInfo.TileColumnCount;
int tileRowIndex = tileNum / this.FrameHeader.TilesInfo.TileColumnCount;