From 6df6f0e61a346b45096809d24696f2e9e8638a00 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 25 Aug 2026 00:14:46 +1000 Subject: [PATCH] Isolate AV1 tile entropy contexts --- .../Heif/Av1/Entropy/Av1Distribution.cs | 70 +++++++++++++++++++ .../Heif/Av1/Entropy/Av1SymbolDecoder.cs | 69 +++++++++++------- .../Heif/Av1/Entropy/Av1SymbolEncoder.cs | 63 +++++++++++------ .../Heif/Av1/Entropy/Av1SymbolReader.cs | 19 +++-- .../Heif/Av1/Entropy/Av1SymbolWriter.cs | 20 ++++-- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 9 ++- 6 files changed, 193 insertions(+), 57 deletions(-) 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;