diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1MotionVectorContext.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1MotionVectorContext.cs
index 5a28d06735..278da523c3 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1MotionVectorContext.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1MotionVectorContext.cs
@@ -107,7 +107,17 @@ internal sealed class Av1MotionVectorContext
/// The spatially derived reference vector.
/// The fractional precision selected by the frame header.
public void Write(Av1SymbolWriter writer, Av1MotionVector value, Av1MotionVector reference, Av1MotionVectorPrecision precision)
- => _ = this.Process(writer, value, reference, precision);
+ => this.Write(writer, value, reference, precision);
+
+ ///
+ /// The operation applied to each motion-vector symbol.
+ public void Write(
+ Av1SymbolWriter writer,
+ Av1MotionVector value,
+ Av1MotionVector reference,
+ Av1MotionVectorPrecision precision)
+ where TOperation : struct, Av1SymbolEncoder.ISymbolOperation
+ => _ = this.Process>(writer, value, reference, precision);
///
/// Measures a motion-vector delta against the live distributions without changing them.
@@ -150,14 +160,12 @@ internal sealed class Av1MotionVectorContext
///
/// Emits motion-vector syntax and reports no estimated rate.
///
- private readonly struct MotionVectorWriteOperation : IMotionVectorSymbolOperation
+ private readonly struct MotionVectorWriteOperation : IMotionVectorSymbolOperation
+ where TOperation : struct, Av1SymbolEncoder.ISymbolOperation
{
///
public static int ProcessSymbol(Av1SymbolWriter writer, int symbol, Av1Distribution distribution)
- {
- writer.WriteSymbol(symbol, distribution);
- return 0;
- }
+ => TOperation.ProcessSymbol(ref writer, symbol, distribution);
}
///
@@ -346,7 +354,7 @@ internal sealed class Av1MotionVectorContext
/// The nonzero component in one-eighth-sample units.
/// The fractional precision selected by the frame header.
public void Write(Av1SymbolWriter writer, int value, Av1MotionVectorPrecision precision)
- => _ = this.Process(writer, value, precision);
+ => _ = this.Process>(writer, value, precision);
///
/// Processes one nonzero signed component through the shared motion-vector symbol operation.
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
index 1d6fb6b805..2c99974983 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
@@ -286,10 +286,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
}
///
- /// Defines how shared coefficient-syntax helpers handle one adaptive symbol or literal bit field.
+ /// Defines how a syntax traversal handles one adaptive symbol or literal bit field.
///
- private interface ICoefficientSymbolOperation
+ public interface ISymbolOperation
{
+ ///
+ /// Gets a value indicating whether this operation emits a bitstream.
+ ///
+ public static abstract bool WritesOutput { get; }
+
///
/// Handles one symbol from an adaptive distribution.
///
@@ -302,6 +307,24 @@ internal sealed class Av1SymbolEncoder : IDisposable
int symbol,
Av1Distribution distribution);
+ ///
+ /// Handles one binary symbol from an adaptive distribution.
+ ///
+ /// The tile range writer.
+ /// The binary symbol.
+ /// The symbol distribution.
+ /// The symbol's rate contribution.
+ public static abstract int ProcessSymbol(ref Av1SymbolWriter writer, bool symbol, Av1Distribution distribution);
+
+ ///
+ /// Handles one binary symbol with a fixed probability.
+ ///
+ /// The tile range writer.
+ /// The binary value.
+ /// The probability of true, scaled by 32768.
+ /// The symbol's rate contribution.
+ public static abstract int ProcessBoolean(ref Av1SymbolWriter writer, bool value, uint frequency);
+
///
/// Handles one most-significant-bit-first literal field.
///
@@ -320,6 +343,11 @@ internal sealed class Av1SymbolEncoder : IDisposable
///
private interface IPaletteColorMapOperation
{
+ ///
+ /// Gets a value indicating whether the traversal retains color tokens for later packing.
+ ///
+ static abstract bool RetainsTokens { get; }
+
///
/// Handles the first uniformly coded palette index.
///
@@ -374,9 +402,18 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The low-order literal bits.
/// The number of bits to write.
public void WriteLiteral(uint value, int bitCount)
+ => this.WriteLiteral(value, bitCount);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteLiteral(uint value, int bitCount)
+ where TOperation : struct, ISymbolOperation
{
- ref Av1SymbolWriter w = ref this.writer;
- w.WriteLiteral(value, bitCount);
+ if (TOperation.WritesOutput)
+ {
+ ref Av1SymbolWriter w = ref this.writer;
+ _ = TOperation.ProcessLiteral(ref w, value, bitCount);
+ }
}
///
@@ -385,20 +422,29 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The number of possible values.
/// The value in the range from zero through minus one.
public void WriteUniform(int valueCount, int value)
+ => this.WriteUniform(valueCount, value);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteUniform(int valueCount, int value)
+ where TOperation : struct, ISymbolOperation
{
- ref Av1SymbolWriter w = ref this.writer;
- int bitCount = Av1Math.Log2(valueCount) + 1;
- int threshold = (1 << bitCount) - valueCount;
- if (value < threshold)
+ if (TOperation.WritesOutput)
{
- // The lower values use the short prefix; every remaining value carries one final disambiguating bit.
- w.WriteLiteral((uint)value, bitCount - 1);
- return;
- }
+ ref Av1SymbolWriter w = ref this.writer;
+ int bitCount = Av1Math.Log2(valueCount) + 1;
+ int threshold = (1 << bitCount) - valueCount;
+ if (value < threshold)
+ {
+ // The lower values use the short prefix; every remaining value carries one final disambiguating bit.
+ _ = TOperation.ProcessLiteral(ref w, (uint)value, bitCount - 1);
+ return;
+ }
- int offset = value - threshold;
- w.WriteLiteral((uint)(threshold + (offset >> 1)), bitCount - 1);
- w.WriteLiteral((uint)(offset & 1), 1);
+ int offset = value - threshold;
+ _ = TOperation.ProcessLiteral(ref w, (uint)(threshold + (offset >> 1)), bitCount - 1);
+ _ = TOperation.ProcessLiteral(ref w, (uint)(offset & 1), 1);
+ }
}
///
@@ -435,9 +481,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The block-area context in the range from zero through six.
/// The number of available above and left luma neighbors that use palettes.
public void WritePaletteYMode(bool usePalette, int blockSizeContext, int neighborContext)
+ => this.WritePaletteYMode(usePalette, blockSizeContext, neighborContext);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WritePaletteYMode(bool usePalette, int blockSizeContext, int neighborContext)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(usePalette, this.entropyContext.PaletteYMode[blockSizeContext][neighborContext]);
+ _ = TOperation.ProcessSymbol(ref w, usePalette, this.entropyContext.PaletteYMode[blockSizeContext][neighborContext]);
}
///
@@ -459,9 +511,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// Indicates whether the block uses chroma palette prediction.
/// Indicates whether the current block uses a luma palette.
public void WritePaletteUvMode(bool usePalette, bool hasLumaPalette)
+ => this.WritePaletteUvMode(usePalette, hasLumaPalette);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WritePaletteUvMode(bool usePalette, bool hasLumaPalette)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(usePalette, this.entropyContext.PaletteUvMode[hasLumaPalette ? 1 : 0]);
+ _ = TOperation.ProcessSymbol(ref w, usePalette, this.entropyContext.PaletteUvMode[hasLumaPalette ? 1 : 0]);
}
///
@@ -487,13 +545,19 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The block-area context in the range from zero through six.
/// The luma or chroma plane class.
public void WritePaletteSize(int paletteSize, int blockSizeContext, Av1PlaneType planeType)
+ => this.WritePaletteSize(paletteSize, blockSizeContext, planeType);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WritePaletteSize(int paletteSize, int blockSizeContext, Av1PlaneType planeType)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
Av1Distribution distribution = planeType == Av1PlaneType.Y
? this.entropyContext.PaletteYSize[blockSizeContext]
: this.entropyContext.PaletteUvSize[blockSizeContext];
- w.WriteSymbol(paletteSize - 2, distribution);
+ _ = TOperation.ProcessSymbol(ref w, paletteSize - 2, distribution);
}
///
@@ -529,13 +593,23 @@ internal sealed class Av1SymbolEncoder : IDisposable
int paletteSize,
int colorContext,
Av1PlaneType planeType)
+ => this.WritePaletteColorIndex(colorOrderIndex, paletteSize, colorContext, planeType);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WritePaletteColorIndex(
+ int colorOrderIndex,
+ int paletteSize,
+ int colorContext,
+ Av1PlaneType planeType)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
Av1Distribution distribution = planeType == Av1PlaneType.Y
? this.entropyContext.PaletteYColorIndex[paletteSize - 2][colorContext]
: this.entropyContext.PaletteUvColorIndex[paletteSize - 2][colorContext];
- w.WriteSymbol(colorOrderIndex, distribution);
+ _ = TOperation.ProcessSymbol(ref w, colorOrderIndex, distribution);
}
///
@@ -575,24 +649,36 @@ internal sealed class Av1SymbolEncoder : IDisposable
ReadOnlySpan colorCache,
ReadOnlySpan colors,
int bitDepth)
- {
- Span cacheColorFound = stackalloc byte[Av1Constants.PaletteMaxSize * 2];
- Span uncachedColors = stackalloc ushort[Av1Constants.PaletteMaxSize];
- int uncachedColorCount = IndexColorCache(
- colorCache,
- colors,
- cacheColorFound,
- uncachedColors);
+ => this.WritePaletteYColors(colorCache, colors, bitDepth);
- int cachedColorCount = 0;
- for (int i = 0; i < colorCache.Length && cachedColorCount < colors.Length; i++)
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WritePaletteYColors(
+ ReadOnlySpan colorCache,
+ ReadOnlySpan colors,
+ int bitDepth)
+ where TOperation : struct, ISymbolOperation
+ {
+ if (TOperation.WritesOutput)
{
- byte found = cacheColorFound[i];
- this.WriteLiteral(found, 1);
- cachedColorCount += found;
- }
+ Span cacheColorFound = stackalloc byte[Av1Constants.PaletteMaxSize * 2];
+ Span uncachedColors = stackalloc ushort[Av1Constants.PaletteMaxSize];
+ int uncachedColorCount = IndexColorCache(
+ colorCache,
+ colors,
+ cacheColorFound,
+ uncachedColors);
+
+ int cachedColorCount = 0;
+ for (int i = 0; i < colorCache.Length && cachedColorCount < colors.Length; i++)
+ {
+ byte found = cacheColorFound[i];
+ this.WriteLiteral(found, 1);
+ cachedColorCount += found;
+ }
- this.WriteDeltaEncodedColors(uncachedColors[..uncachedColorCount], bitDepth, minimumDelta: 1);
+ this.WriteDeltaEncodedColors(uncachedColors[..uncachedColorCount], bitDepth, minimumDelta: 1);
+ }
}
///
@@ -640,61 +726,74 @@ internal sealed class Av1SymbolEncoder : IDisposable
ReadOnlySpan uColors,
ReadOnlySpan vColors,
int bitDepth)
- {
- Span cacheColorFound = stackalloc byte[Av1Constants.PaletteMaxSize * 2];
- Span uncachedColors = stackalloc ushort[Av1Constants.PaletteMaxSize];
- int uncachedColorCount = IndexColorCache(
- colorCache,
- uColors,
- cacheColorFound,
- uncachedColors);
-
- int cachedColorCount = 0;
- for (int i = 0; i < colorCache.Length && cachedColorCount < uColors.Length; i++)
- {
- byte found = cacheColorFound[i];
- this.WriteLiteral(found, 1);
- cachedColorCount += found;
- }
+ => this.WritePaletteUvColors(colorCache, uColors, vColors, bitDepth);
- this.WriteDeltaEncodedColors(uncachedColors[..uncachedColorCount], bitDepth, minimumDelta: 0);
-
- int deltaBits = GetPaletteVDeltaBitCount(vColors, bitDepth, out int zeroCount, out int minimumBits);
- int deltaBitCount = 2 + bitDepth + ((deltaBits + 1) * (vColors.Length - 1)) - zeroCount;
- int rawBitCount = bitDepth * vColors.Length;
- bool useDelta = deltaBitCount < rawBitCount;
- this.WriteLiteral(useDelta ? 1u : 0u, 1);
- if (!useDelta)
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WritePaletteUvColors(
+ ReadOnlySpan colorCache,
+ ReadOnlySpan uColors,
+ ReadOnlySpan vColors,
+ int bitDepth)
+ where TOperation : struct, ISymbolOperation
+ {
+ if (TOperation.WritesOutput)
{
- for (int i = 0; i < vColors.Length; i++)
+ Span cacheColorFound = stackalloc byte[Av1Constants.PaletteMaxSize * 2];
+ Span uncachedColors = stackalloc ushort[Av1Constants.PaletteMaxSize];
+ int uncachedColorCount = IndexColorCache(
+ colorCache,
+ uColors,
+ cacheColorFound,
+ uncachedColors);
+
+ int cachedColorCount = 0;
+ for (int i = 0; i < colorCache.Length && cachedColorCount < uColors.Length; i++)
{
- this.WriteLiteral(vColors[i], bitDepth);
+ byte found = cacheColorFound[i];
+ this.WriteLiteral(found, 1);
+ cachedColorCount += found;
}
- return;
- }
-
- this.WriteLiteral((uint)(deltaBits - minimumBits), 2);
- this.WriteLiteral(vColors[0], bitDepth);
- int sampleRange = 1 << bitDepth;
- for (int i = 1; i < vColors.Length; i++)
- {
- int signedDelta = vColors[i] - vColors[i - 1];
- int delta = Math.Abs(signedDelta);
+ this.WriteDeltaEncodedColors(uncachedColors[..uncachedColorCount], bitDepth, minimumDelta: 0);
- // Chroma wraps in its unsigned sample domain, so signal whichever circular direction has less magnitude.
- if (delta <= sampleRange - delta)
+ int deltaBits = GetPaletteVDeltaBitCount(vColors, bitDepth, out int zeroCount, out int minimumBits);
+ int deltaBitCount = 2 + bitDepth + ((deltaBits + 1) * (vColors.Length - 1)) - zeroCount;
+ int rawBitCount = bitDepth * vColors.Length;
+ bool useDelta = deltaBitCount < rawBitCount;
+ this.WriteLiteral(useDelta ? 1u : 0u, 1);
+ if (!useDelta)
{
- this.WriteLiteral((uint)delta, deltaBits);
- if (delta != 0)
+ for (int i = 0; i < vColors.Length; i++)
{
- this.WriteLiteral(signedDelta < 0 ? 1u : 0u, 1);
+ this.WriteLiteral(vColors[i], bitDepth);
}
+
+ return;
}
- else
+
+ this.WriteLiteral((uint)(deltaBits - minimumBits), 2);
+ this.WriteLiteral(vColors[0], bitDepth);
+ int sampleRange = 1 << bitDepth;
+ for (int i = 1; i < vColors.Length; i++)
{
- this.WriteLiteral((uint)(sampleRange - delta), deltaBits);
- this.WriteLiteral(signedDelta < 0 ? 0u : 1u, 1);
+ int signedDelta = vColors[i] - vColors[i - 1];
+ int delta = Math.Abs(signedDelta);
+
+ // Chroma wraps in its unsigned sample domain, so signal whichever circular direction has less magnitude.
+ if (delta <= sampleRange - delta)
+ {
+ this.WriteLiteral((uint)delta, deltaBits);
+ if (delta != 0)
+ {
+ this.WriteLiteral(signedDelta < 0 ? 1u : 0u, 1);
+ }
+ }
+ else
+ {
+ this.WriteLiteral((uint)(sampleRange - delta), deltaBits);
+ this.WriteLiteral(signedDelta < 0 ? 0u : 1u, 1);
+ }
}
}
}
@@ -719,7 +818,8 @@ internal sealed class Av1SymbolEncoder : IDisposable
planeType,
rows,
columns,
- colorIndexMap);
+ colorIndexMap,
+ Span.Empty);
///
/// Writes a complete palette color-index map in AV1 diagonal wavefront order.
@@ -735,13 +835,67 @@ internal sealed class Av1SymbolEncoder : IDisposable
int rows,
int columns,
Buffer2DRegion colorIndexMap)
+ => this.WritePaletteColorMap(paletteSize, planeType, rows, columns, colorIndexMap);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WritePaletteColorMap(
+ int paletteSize,
+ Av1PlaneType planeType,
+ int rows,
+ int columns,
+ Buffer2DRegion colorIndexMap)
+ where TOperation : struct, ISymbolOperation
{
- _ = this.ProcessPaletteColorMap(
+ _ = this.ProcessPaletteColorMap>(
paletteSize,
planeType,
rows,
columns,
- colorIndexMap);
+ colorIndexMap,
+ Span.Empty);
+ }
+
+ ///
+ /// Retains palette color tokens and updates their adaptive probabilities without writing output bytes.
+ ///
+ /// The number of colors in the palette.
+ /// The luma or chroma plane class.
+ /// The number of coded map rows.
+ /// The number of coded map columns.
+ /// The selected color-index map.
+ /// The destination with one byte per coded sample.
+ public void TokenizePaletteColorMap(
+ int paletteSize,
+ Av1PlaneType planeType,
+ int rows,
+ int columns,
+ Buffer2DRegion colorIndexMap,
+ Span tokens)
+ {
+ _ = this.ProcessPaletteColorMap(
+ paletteSize,
+ planeType,
+ rows,
+ columns,
+ colorIndexMap,
+ tokens);
+ }
+
+ ///
+ /// Writes retained palette tokens in their previously selected order.
+ ///
+ /// The number of colors in the palette.
+ /// The luma or chroma plane class.
+ /// The raw first index followed by packed context and color-rank tokens.
+ public void WritePaletteTokens(int paletteSize, Av1PlaneType planeType, ReadOnlySpan tokens)
+ {
+ this.WriteUniform(paletteSize, tokens[0]);
+ for (int i = 1; i < tokens.Length; i++)
+ {
+ byte token = tokens[i];
+ this.WritePaletteColorIndex(token & 7, paletteSize, token >> 4, planeType);
+ }
}
///
@@ -749,9 +903,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
///
/// Indicates whether intra-block copy is selected.
public void WriteUseIntraBlockCopy(bool value)
+ => this.WriteUseIntraBlockCopy(value);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteUseIntraBlockCopy(bool value)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(value, this.tileIntraBlockCopy);
+ _ = TOperation.ProcessSymbol(ref w, value, this.tileIntraBlockCopy);
}
///
@@ -768,7 +928,13 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The displacement vector to encode.
/// The spatially derived reference vector.
public void WriteDisplacementVector(Av1MotionVector value, Av1MotionVector reference)
- => this.displacementVector.Write(this.writer, value, reference, Av1MotionVectorPrecision.Integer);
+ => this.WriteDisplacementVector(value, reference);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteDisplacementVector(Av1MotionVector value, Av1MotionVector reference)
+ where TOperation : struct, ISymbolOperation
+ => this.displacementVector.Write(this.writer, value, reference, Av1MotionVectorPrecision.Integer);
///
/// Measures an integer intra-block-copy displacement vector against the live distributions.
@@ -818,7 +984,13 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The regular, smooth, or sharp filter.
/// The spatial filter context for the selected direction.
public void WriteSwitchableInterpolationFilter(Av1InterpolationFilter filter, int context)
- => this.writer.WriteSymbol((int)filter, this.entropyContext.SwitchableInterpolation[context]);
+ => this.WriteSwitchableInterpolationFilter(filter, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteSwitchableInterpolationFilter(Av1InterpolationFilter filter, int context)
+ where TOperation : struct, ISymbolOperation
+ => TOperation.ProcessSymbol(ref this.writer, (int)filter, this.entropyContext.SwitchableInterpolation[context]);
///
/// Measures a single-reference inter mode against the live branch distributions.
@@ -859,23 +1031,30 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The new, global, nearest, or near motion-vector mode.
/// The packed context derived from the reference-vector stack.
public void WriteInterMode(Av1PredictionMode mode, int modeContext)
+ => this.WriteInterMode(mode, modeContext);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteInterMode(Av1PredictionMode mode, int modeContext)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
bool isNotNew = mode != Av1PredictionMode.NewMotionVector;
- w.WriteSymbol(isNotNew, this.newMotionVector[Av1SymbolContextHelper.GetNewMvContext(modeContext)]);
+ _ = TOperation.ProcessSymbol(ref w, isNotNew, this.newMotionVector[Av1SymbolContextHelper.GetNewMvContext(modeContext)]);
if (!isNotNew)
{
return;
}
bool isNotGlobal = mode != Av1PredictionMode.GlobalMotionVector;
- w.WriteSymbol(isNotGlobal, this.zeroMotionVector[Av1SymbolContextHelper.GetZeroMvContext(modeContext)]);
+ _ = TOperation.ProcessSymbol(ref w, isNotGlobal, this.zeroMotionVector[Av1SymbolContextHelper.GetZeroMvContext(modeContext)]);
if (!isNotGlobal)
{
return;
}
- w.WriteSymbol(
+ _ = TOperation.ProcessSymbol(
+ ref w,
mode == Av1PredictionMode.NearMotionVector,
this.referenceMotionVector[Av1SymbolContextHelper.GetRefMvContext(modeContext)]);
}
@@ -895,9 +1074,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// Whether selection advances to the next candidate.
/// The candidate-weight context.
public void WriteDynamicReferenceList(bool advance, int context)
+ => this.WriteDynamicReferenceList(advance, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteDynamicReferenceList(bool advance, int context)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(advance, this.dynamicReferenceList[context]);
+ _ = TOperation.ProcessSymbol(ref w, advance, this.dynamicReferenceList[context]);
}
///
@@ -929,7 +1114,16 @@ internal sealed class Av1SymbolEncoder : IDisposable
Av1MotionVector value,
Av1MotionVector reference,
Av1MotionVectorPrecision precision)
- => this.motionVector.Write(this.writer, value, reference, precision);
+ => this.WriteMotionVector(value, reference, precision);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteMotionVector(
+ Av1MotionVector value,
+ Av1MotionVector reference,
+ Av1MotionVectorPrecision precision)
+ where TOperation : struct, ISymbolOperation
+ => this.motionVector.Write(this.writer, value, reference, precision);
///
/// Gets the current fixed-point cost of a complete block partition symbol.
@@ -946,9 +1140,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The partition type to encode.
/// The partition probability context.
public void WritePartitionType(Av1PartitionType partitionType, int context)
+ => this.WritePartitionType(partitionType, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WritePartitionType(Av1PartitionType partitionType, int context)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol((int)partitionType, this.tilePartitionTypes[context]);
+ _ = TOperation.ProcessSymbol(ref w, (int)partitionType, this.tilePartitionTypes[context]);
}
///
@@ -958,11 +1158,20 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The current block size.
/// The partition probability context.
public void WriteSplitOrHorizontal(Av1PartitionType partitionType, Av1BlockSize blockSize, int context)
+ => this.WriteSplitOrHorizontal(partitionType, blockSize, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteSplitOrHorizontal(Av1PartitionType partitionType, Av1BlockSize blockSize, int context)
+ where TOperation : struct, ISymbolOperation
{
- uint frequency = Av1SymbolDecoder.GetSplitOrHorizontalFrequency(this.tilePartitionTypes, blockSize, context);
- bool value = partitionType == Av1PartitionType.Split;
- ref Av1SymbolWriter w = ref this.writer;
- w.WriteBoolean(value, frequency);
+ if (TOperation.WritesOutput)
+ {
+ uint frequency = Av1SymbolDecoder.GetSplitOrHorizontalFrequency(this.tilePartitionTypes, blockSize, context);
+ bool value = partitionType == Av1PartitionType.Split;
+ ref Av1SymbolWriter w = ref this.writer;
+ _ = TOperation.ProcessBoolean(ref w, value, frequency);
+ }
}
///
@@ -992,11 +1201,20 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The current block size.
/// The partition probability context.
public void WriteSplitOrVertical(Av1PartitionType partitionType, Av1BlockSize blockSize, int context)
+ => this.WriteSplitOrVertical(partitionType, blockSize, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteSplitOrVertical(Av1PartitionType partitionType, Av1BlockSize blockSize, int context)
+ where TOperation : struct, ISymbolOperation
{
- uint frequency = Av1SymbolDecoder.GetSplitOrVerticalFrequency(this.tilePartitionTypes, blockSize, context);
- bool value = partitionType == Av1PartitionType.Split;
- ref Av1SymbolWriter w = ref this.writer;
- w.WriteBoolean(value, frequency);
+ if (TOperation.WritesOutput)
+ {
+ uint frequency = Av1SymbolDecoder.GetSplitOrVerticalFrequency(this.tilePartitionTypes, blockSize, context);
+ bool value = partitionType == Av1PartitionType.Split;
+ ref Av1SymbolWriter w = ref this.writer;
+ _ = TOperation.ProcessBoolean(ref w, value, frequency);
+ }
}
///
@@ -1044,12 +1262,51 @@ internal sealed class Av1SymbolEncoder : IDisposable
bool useReducedTransformSet,
Av1FilterIntraMode filterIntraMode,
bool usesInterTransformSet)
+ => this.WriteCoefficients(
+ transformSize,
+ transformType,
+ intraDirection,
+ coefficientBuffer,
+ componentType,
+ transformBlockContext,
+ endOfBlock,
+ useReducedTransformSet,
+ filterIntraMode,
+ usesInterTransformSet);
+
+ ///
+ /// Processes finalized coefficient symbols and returns the neighboring coefficient context.
+ ///
+ /// The operation applied to each symbol and literal.
+ /// The transform dimensions.
+ /// The selected transform type.
+ /// The luma prediction mode.
+ /// The quantized raster coefficients.
+ /// The luma or chroma component.
+ /// The neighboring skip and DC sign contexts.
+ /// The one-based final nonzero scan position, or zero for an empty transform.
+ /// Whether the reduced transform set applies.
+ /// The filter-intra prediction mode.
+ /// Whether inter transform syntax applies.
+ /// The coefficient context consumed by adjacent transforms.
+ public int WriteCoefficients(
+ Av1TransformSize transformSize,
+ Av1TransformType transformType,
+ Av1PredictionMode intraDirection,
+ ReadOnlySpan coefficientBuffer,
+ Av1ComponentType componentType,
+ Av1TransformBlockContext transformBlockContext,
+ ushort endOfBlock,
+ bool useReducedTransformSet,
+ Av1FilterIntraMode filterIntraMode,
+ bool usesInterTransformSet)
+ where TOperation : struct, ISymbolOperation
{
Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize);
DebugGuard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext));
- _ = this.ProcessTransformBlockSkip(
+ _ = this.ProcessTransformBlockSkip(
endOfBlock == 0,
transformSizeContext,
transformBlockContext.SkipContext);
@@ -1073,7 +1330,7 @@ internal sealed class Av1SymbolEncoder : IDisposable
levels.Initialize(coefficientBuffer);
if (componentType == Av1ComponentType.Luminance)
{
- _ = this.ProcessTransformType(
+ _ = this.ProcessTransformType(
transformType,
transformSize,
usesInterTransformSet,
@@ -1083,7 +1340,7 @@ internal sealed class Av1SymbolEncoder : IDisposable
intraDirection);
}
- _ = this.ProcessEndOfBlockPosition(
+ _ = this.ProcessEndOfBlockPosition(
endOfBlock,
componentType,
transformClass,
@@ -1103,13 +1360,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
if (c == endOfBlock - 1)
{
- w.WriteSymbol(
+ _ = TOperation.ProcessSymbol(
+ ref w,
Math.Min(level, 3) - 1,
this.coefficientsBaseEndOfBlock[(int)transformSizeContext][(int)componentType][coefficientContext]);
}
else
{
- w.WriteSymbol(
+ _ = TOperation.ProcessSymbol(
+ ref w,
Math.Min(level, 3),
this.coefficientsBase[(int)transformSizeContext][(int)componentType][coefficientContext]);
}
@@ -1122,7 +1381,8 @@ internal sealed class Av1SymbolEncoder : IDisposable
for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1)
{
int symbol = Math.Min(baseRange - idx, Av1Constants.BaseRangeSizeMinus1);
- w.WriteSymbol(
+ _ = TOperation.ProcessSymbol(
+ ref w,
symbol,
this.coefficientsBaseRange[limitedTransformSizeContext][(int)componentType][baseRangeContext]);
@@ -1148,18 +1408,19 @@ internal sealed class Av1SymbolEncoder : IDisposable
{
if (c == 0)
{
- w.WriteSymbol(
+ _ = TOperation.ProcessSymbol(
+ ref w,
(int)sign,
this.dcSign[(int)componentType][transformBlockContext.DcSignContext]);
}
else
{
- w.WriteLiteral(sign, 1);
+ _ = TOperation.ProcessLiteral(ref w, sign, 1);
}
if (level > (Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount))
{
- this.WriteGolomb(
+ this.WriteGolomb(
level - Av1Constants.CoefficientBaseRange - 1 - Av1Constants.BaseLevelsCount);
}
}
@@ -1361,8 +1622,19 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The signaled transform size selecting the token alphabet.
/// The square transform-size probability context.
public void WriteEndOfBlockPosition(ushort endOfBlock, Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, Av1TransformSize transformSizeContext)
+ => this.WriteEndOfBlockPosition(endOfBlock, componentType, transformClass, transformSize, transformSizeContext);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteEndOfBlockPosition(
+ ushort endOfBlock,
+ Av1ComponentType componentType,
+ Av1TransformClass transformClass,
+ Av1TransformSize transformSize,
+ Av1TransformSize transformSizeContext)
+ where TOperation : struct, ISymbolOperation
{
- _ = this.ProcessEndOfBlockPosition(
+ _ = this.ProcessEndOfBlockPosition(
endOfBlock,
componentType,
transformClass,
@@ -1376,7 +1648,7 @@ internal sealed class Av1SymbolEncoder : IDisposable
Av1TransformClass transformClass,
Av1TransformSize transformSize,
Av1TransformSize transformSizeContext)
- where TOperation : struct, ICoefficientSymbolOperation
+ where TOperation : struct, ISymbolOperation
{
short endOfBlockPosition = Av1SymbolContextHelper.GetEndOfBlockPosition(endOfBlock, out int eobExtra);
int rate = this.ProcessEndOfBlockFlag(
@@ -1425,15 +1697,21 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The square transform-size probability context.
/// The context derived from neighboring coefficient blocks.
public void WriteTransformBlockSkip(bool skip, Av1TransformSize transformSizeContext, int skipContext)
+ => this.WriteTransformBlockSkip(skip, transformSizeContext, skipContext);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteTransformBlockSkip(bool skip, Av1TransformSize transformSizeContext, int skipContext)
+ where TOperation : struct, ISymbolOperation
{
- _ = this.ProcessTransformBlockSkip(skip, transformSizeContext, skipContext);
+ _ = this.ProcessTransformBlockSkip(skip, transformSizeContext, skipContext);
}
private int ProcessTransformBlockSkip(
bool skip,
Av1TransformSize transformSizeContext,
int skipContext)
- where TOperation : struct, ICoefficientSymbolOperation
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
return TOperation.ProcessSymbol(
@@ -1462,10 +1740,16 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The selected transform size.
/// The neighboring transform-size context.
public void WriteTransformSize(Av1BlockSize blockSize, Av1TransformSize transformSize, int context)
+ => this.WriteTransformSize(blockSize, transformSize, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteTransformSize(Av1BlockSize blockSize, Av1TransformSize transformSize, int context)
+ where TOperation : struct, ISymbolOperation
{
int selectedDepth = GetTransformSizeDepth(blockSize, transformSize, out int categoryDepth);
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(selectedDepth, this.transformSize[categoryDepth - 1][context]);
+ _ = TOperation.ProcessSymbol(ref w, selectedDepth, this.transformSize[categoryDepth - 1][context]);
}
///
@@ -1483,9 +1767,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// Indicates whether the current transform node is split.
/// The neighboring variable-transform context.
public void WriteTransformPartition(bool split, int context)
+ => this.WriteTransformPartition(split, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteTransformPartition(bool split, int context)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(split ? 1 : 0, this.transformPartition[context]);
+ _ = TOperation.ProcessSymbol(ref w, split ? 1 : 0, this.transformPartition[context]);
}
private static int GetTransformSizeDepth(
@@ -1556,12 +1846,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
///
/// The nonnegative suffix value.
public void WriteGolomb(int level)
+ => this.WriteGolomb(level);
+
+ private void WriteGolomb(int level)
+ where TOperation : struct, ISymbolOperation
{
uint x = (uint)level + 1u;
int length = GetGolombBitLength(level);
- ref Av1SymbolWriter w = ref this.writer;
- w.WriteLiteral(0u, length - 1);
- w.WriteLiteral(x, length);
+ _ = TOperation.ProcessLiteral(ref this.writer, 0u, length - 1);
+ _ = TOperation.ProcessLiteral(ref this.writer, x, length);
}
private static int GetBaseRangeCost(int level, Av1Distribution distribution)
@@ -1611,7 +1904,7 @@ internal sealed class Av1SymbolEncoder : IDisposable
Av1TransformClass transformClass,
Av1TransformSize transformSize,
int endOfBlockPosition)
- where TOperation : struct, ICoefficientSymbolOperation
+ where TOperation : struct, ISymbolOperation
{
int endOfBlockMultiSize = transformSize.GetLog2Minus4();
int endOfBlockContext = transformClass == Av1TransformClass.Class2D ? 0 : 1;
@@ -1668,8 +1961,28 @@ internal sealed class Av1SymbolEncoder : IDisposable
Av1FilterIntraMode filterIntraMode,
Av1PredictionMode intraDirection,
bool usesInterTransformSet)
+ => this.WriteTransformType(
+ transformType,
+ transformSize,
+ useReducedTransformSet,
+ baseQIndex,
+ filterIntraMode,
+ intraDirection,
+ usesInterTransformSet);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteTransformType(
+ Av1TransformType transformType,
+ Av1TransformSize transformSize,
+ bool useReducedTransformSet,
+ int baseQIndex,
+ Av1FilterIntraMode filterIntraMode,
+ Av1PredictionMode intraDirection,
+ bool usesInterTransformSet)
+ where TOperation : struct, ISymbolOperation
{
- _ = this.ProcessTransformType(
+ _ = this.ProcessTransformType(
transformType,
transformSize,
usesInterTransformSet,
@@ -1687,7 +2000,7 @@ internal sealed class Av1SymbolEncoder : IDisposable
int baseQIndex,
Av1FilterIntraMode filterIntraMode,
Av1PredictionMode intraDirection)
- where TOperation : struct, ICoefficientSymbolOperation
+ where TOperation : struct, ISymbolOperation
{
Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(
transformSize,
@@ -1742,9 +2055,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The segment identifier.
/// The context derived from neighboring segment identifiers.
public void WriteSegmentId(int segmentId, int context)
+ => this.WriteSegmentId(segmentId, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteSegmentId(int segmentId, int context)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(segmentId, this.segmentId[context]);
+ _ = TOperation.ProcessSymbol(ref w, segmentId, this.segmentId[context]);
}
///
@@ -1762,9 +2081,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// Indicates whether the block contains no coded transform coefficients.
/// The neighboring skip context.
public void WriteSkip(bool skip, int context)
+ => this.WriteSkip(skip, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteSkip(bool skip, int context)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(skip, this.skip[context]);
+ _ = TOperation.ProcessSymbol(ref w, skip, this.skip[context]);
}
///
@@ -1773,9 +2098,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// Indicates whether skip mode is selected.
/// The neighboring skip-mode context.
public void WriteSkipMode(bool skip, int context)
+ => this.WriteSkipMode(skip, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteSkipMode(bool skip, int context)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(skip, this.skipMode[context]);
+ _ = TOperation.ProcessSymbol(ref w, skip, this.skipMode[context]);
}
///
@@ -1802,13 +2133,19 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The selected filter-intra mode, or the disabled sentinel.
/// The block size selecting the enable distribution.
public void WriteFilterIntraMode(Av1FilterIntraMode filterIntraMode, Av1BlockSize blockSize)
+ => this.WriteFilterIntraMode(filterIntraMode, blockSize);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteFilterIntraMode(Av1FilterIntraMode filterIntraMode, Av1BlockSize blockSize)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
bool useFilter = filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes;
- w.WriteSymbol(useFilter, this.filterIntra[(int)blockSize]);
+ _ = TOperation.ProcessSymbol(ref w, useFilter, this.filterIntra[(int)blockSize]);
if (useFilter)
{
- w.WriteSymbol((int)filterIntraMode, this.filterIntraMode);
+ _ = TOperation.ProcessSymbol(ref w, (int)filterIntraMode, this.filterIntraMode);
}
}
@@ -1817,26 +2154,32 @@ internal sealed class Av1SymbolEncoder : IDisposable
///
/// The signed quantizer-index delta.
public void WriteDeltaQuantizerIndex(int deltaQindex)
+ => this.WriteDeltaQuantizerIndex(deltaQindex);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteDeltaQuantizerIndex(int deltaQindex)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
bool sign = deltaQindex < 0;
int abs = Math.Abs(deltaQindex);
bool isSmallValue = abs < Av1Constants.DeltaQuantizerSmall;
- w.WriteSymbol(Math.Min(abs, Av1Constants.DeltaQuantizerSmall), this.deltaQuantizerAbsolute);
+ _ = TOperation.ProcessSymbol(ref w, Math.Min(abs, Av1Constants.DeltaQuantizerSmall), this.deltaQuantizerAbsolute);
if (!isSmallValue)
{
// Escape magnitudes encode their bit width first, followed by the offset within that width's range.
int remainingBitCount = Av1Math.MostSignificantBit((uint)(abs - 1));
int threshold = (1 << remainingBitCount) + 1;
- w.WriteLiteral((uint)(remainingBitCount - 1), 3);
- w.WriteLiteral((uint)(abs - threshold), remainingBitCount);
+ _ = TOperation.ProcessLiteral(ref w, (uint)(remainingBitCount - 1), 3);
+ _ = TOperation.ProcessLiteral(ref w, (uint)(abs - threshold), remainingBitCount);
}
if (abs > 0)
{
- w.WriteLiteral(sign);
+ _ = TOperation.ProcessLiteral(ref w, sign ? 1u : 0u, 1);
}
}
@@ -1859,9 +2202,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The reduced above-mode context.
/// The reduced left-mode context.
public void WriteLumaMode(Av1PredictionMode lumaMode, byte topContext, byte leftContext)
+ => this.WriteLumaMode(lumaMode, topContext, leftContext);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteLumaMode(Av1PredictionMode lumaMode, byte topContext, byte leftContext)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol((int)lumaMode, this.keyFrameYMode[topContext][leftContext]);
+ _ = TOperation.ProcessSymbol(ref w, (int)lumaMode, this.keyFrameYMode[topContext][leftContext]);
}
///
@@ -1879,9 +2228,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The intra luma mode.
/// The coding block size selecting the size group.
public void WriteInterFrameLumaMode(Av1PredictionMode lumaMode, Av1BlockSize blockSize)
+ => this.WriteInterFrameLumaMode(lumaMode, blockSize);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteInterFrameLumaMode(Av1PredictionMode lumaMode, Av1BlockSize blockSize)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol((int)lumaMode, this.frameYMode[blockSize.GetSizeGroup()]);
+ _ = TOperation.ProcessSymbol(ref w, (int)lumaMode, this.frameYMode[blockSize.GetSizeGroup()]);
}
///
@@ -1899,9 +2254,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// Whether the block uses a retained reference frame.
/// The neighboring prediction-domain context.
public void WriteIsInter(bool isInter, int context)
+ => this.WriteIsInter(isInter, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteIsInter(bool isInter, int context)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(isInter, this.intraInter[context]);
+ _ = TOperation.ProcessSymbol(ref w, isInter, this.intraInter[context]);
}
///
@@ -1958,23 +2319,32 @@ internal sealed class Av1SymbolEncoder : IDisposable
public void WriteSingleReference(
Av1ReferenceFrameType referenceFrame,
ReadOnlySpan referenceCounts)
+ => this.WriteSingleReference(referenceFrame, referenceCounts);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteSingleReference(
+ Av1ReferenceFrameType referenceFrame,
+ ReadOnlySpan referenceCounts)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
bool isBackward = referenceFrame >= Av1ReferenceFrameType.Backward;
int context = Av1SymbolContextHelper.GetSingleReferenceBackwardContext(referenceCounts);
- w.WriteSymbol(isBackward, this.singleReference[context][0]);
+ _ = TOperation.ProcessSymbol(ref w, isBackward, this.singleReference[context][0]);
if (isBackward)
{
bool isAlternate = referenceFrame == Av1ReferenceFrameType.Alternate;
context = Av1SymbolContextHelper.GetSingleReferenceAlternateContext(referenceCounts);
- w.WriteSymbol(isAlternate, this.singleReference[context][1]);
+ _ = TOperation.ProcessSymbol(ref w, isAlternate, this.singleReference[context][1]);
if (isAlternate)
{
return;
}
context = Av1SymbolContextHelper.GetSingleReferenceAlternate2Context(referenceCounts);
- w.WriteSymbol(
+ _ = TOperation.ProcessSymbol(
+ ref w,
referenceFrame == Av1ReferenceFrameType.Alternate2,
this.singleReference[context][5]);
@@ -1983,11 +2353,12 @@ internal sealed class Av1SymbolEncoder : IDisposable
bool isLast3OrGolden = referenceFrame is Av1ReferenceFrameType.Last3 or Av1ReferenceFrameType.Golden;
context = Av1SymbolContextHelper.GetSingleReferenceLast3OrGoldenContext(referenceCounts);
- w.WriteSymbol(isLast3OrGolden, this.singleReference[context][2]);
+ _ = TOperation.ProcessSymbol(ref w, isLast3OrGolden, this.singleReference[context][2]);
if (isLast3OrGolden)
{
context = Av1SymbolContextHelper.GetSingleReferenceGoldenContext(referenceCounts);
- w.WriteSymbol(
+ _ = TOperation.ProcessSymbol(
+ ref w,
referenceFrame == Av1ReferenceFrameType.Golden,
this.singleReference[context][4]);
@@ -1995,7 +2366,8 @@ internal sealed class Av1SymbolEncoder : IDisposable
}
context = Av1SymbolContextHelper.GetSingleReferenceLast2Context(referenceCounts);
- w.WriteSymbol(
+ _ = TOperation.ProcessSymbol(
+ ref w,
referenceFrame == Av1ReferenceFrameType.Last2,
this.singleReference[context][3]);
}
@@ -2017,9 +2389,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The signed angle delta offset by .
/// The directional prediction mode selecting the distribution.
public void WriteAngleDelta(int angleDelta, Av1PredictionMode context)
+ => this.WriteAngleDelta(angleDelta, context);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteAngleDelta(int angleDelta, Av1PredictionMode context)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(angleDelta, this.angleDelta[context - Av1PredictionMode.Vertical]);
+ _ = TOperation.ProcessSymbol(ref w, angleDelta, this.angleDelta[context - Av1PredictionMode.Vertical]);
}
///
@@ -2028,9 +2406,18 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The CDEF strength index.
/// The number of signaled bits.
public void WriteCdefStrength(int cdefStrength, int bitCount)
+ => this.WriteCdefStrength(cdefStrength, bitCount);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteCdefStrength(int cdefStrength, int bitCount)
+ where TOperation : struct, ISymbolOperation
{
- ref Av1SymbolWriter w = ref this.writer;
- w.WriteLiteral((uint)cdefStrength, bitCount);
+ if (TOperation.WritesOutput)
+ {
+ ref Av1SymbolWriter w = ref this.writer;
+ _ = TOperation.ProcessLiteral(ref w, (uint)cdefStrength, bitCount);
+ }
}
///
@@ -2081,10 +2468,16 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// Indicates whether chroma-from-luma is valid for the block.
/// The block's luma prediction mode.
public void WriteChromaMode(Av1ChromaPredictionMode chromaMode, bool isChromaFromLumaAllowed, Av1PredictionMode lumaMode)
+ => this.WriteChromaMode(chromaMode, isChromaFromLumaAllowed, lumaMode);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteChromaMode(Av1ChromaPredictionMode chromaMode, bool isChromaFromLumaAllowed, Av1PredictionMode lumaMode)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
int cflAllowed = isChromaFromLumaAllowed ? 1 : 0;
- w.WriteSymbol((int)chromaMode, this.uvMode[cflAllowed][(int)lumaMode]);
+ _ = TOperation.ProcessSymbol(ref w, (int)chromaMode, this.uvMode[cflAllowed][(int)lumaMode]);
}
///
@@ -2093,9 +2486,15 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The packed U/V alpha-magnitude indices.
/// The joint U/V sign symbol.
public void WriteChromaFromLumaAlphas(int chromaFromLumaIndex, int joinedSign)
+ => this.WriteChromaFromLumaAlphas(chromaFromLumaIndex, joinedSign);
+
+ ///
+ /// The operation applied to each symbol and literal.
+ public void WriteChromaFromLumaAlphas(int chromaFromLumaIndex, int joinedSign)
+ where TOperation : struct, ISymbolOperation
{
ref Av1SymbolWriter w = ref this.writer;
- w.WriteSymbol(joinedSign, this.chromaFromLumaSign);
+ _ = TOperation.ProcessSymbol(ref w, joinedSign, this.chromaFromLumaSign);
// Magnitudes are only signaled for nonzero signs; the shared helper keeps encoder and decoder mappings exact.
int signU = Av1ChromaFromLumaMath.SignU(joinedSign);
@@ -2103,7 +2502,7 @@ internal sealed class Av1SymbolEncoder : IDisposable
{
int contextU = Av1ChromaFromLumaMath.ContextU(joinedSign);
int indexU = Av1ChromaFromLumaMath.IndexU(chromaFromLumaIndex);
- w.WriteSymbol(indexU, this.chromaFromLumaAlpha[contextU]);
+ _ = TOperation.ProcessSymbol(ref w, indexU, this.chromaFromLumaAlpha[contextU]);
}
int signV = Av1ChromaFromLumaMath.SignV(joinedSign);
@@ -2111,7 +2510,7 @@ internal sealed class Av1SymbolEncoder : IDisposable
{
int contextV = Av1ChromaFromLumaMath.ContextV(joinedSign);
int indexV = Av1ChromaFromLumaMath.IndexV(chromaFromLumaIndex);
- w.WriteSymbol(indexV, this.chromaFromLumaAlpha[contextV]);
+ _ = TOperation.ProcessSymbol(ref w, indexV, this.chromaFromLumaAlpha[contextV]);
}
}
@@ -2125,16 +2524,24 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The number of coded map columns.
/// The complete row-addressable color-index map.
/// The rate cost in 1/512-bit units, or zero while writing.
+ /// The token destination for retaining operations; otherwise an empty span.
private int ProcessPaletteColorMap(
int paletteSize,
Av1PlaneType planeType,
int rows,
int columns,
- Buffer2DRegion colorIndexMap)
+ Buffer2DRegion colorIndexMap,
+ Span tokens)
where TOperation : struct, IPaletteColorMapOperation
{
int colorIndex = colorIndexMap.DangerousGetRowSpan(0)[0];
int cost = TOperation.ProcessFirstIndex(this, paletteSize, colorIndex);
+ if (TOperation.RetainsTokens)
+ {
+ tokens[0] = (byte)colorIndex;
+ }
+
+ int tokenIndex = 1;
Span colorOrder = stackalloc byte[Av1Constants.PaletteMaxSize];
for (int diagonal = 1; diagonal < rows + columns - 1; diagonal++)
{
@@ -2153,6 +2560,13 @@ internal sealed class Av1SymbolEncoder : IDisposable
colorOrder,
out int colorOrderIndex);
+ if (TOperation.RetainsTokens)
+ {
+ // Three low bits retain the color rank; the upper nibble retains its spatial context.
+ // Packing later reads this byte without consulting a reused prediction map.
+ tokens[tokenIndex++] = (byte)((colorContext << 4) | colorOrderIndex);
+ }
+
cost += TOperation.ProcessColorIndex(
this,
paletteSize,
@@ -2264,17 +2678,18 @@ internal sealed class Av1SymbolEncoder : IDisposable
/// The sorted colors.
/// The number of bits in each color sample.
/// The minimum representable difference between adjacent colors.
- private void WriteDeltaEncodedColors(
+ private void WriteDeltaEncodedColors(
ReadOnlySpan colors,
int bitDepth,
int minimumDelta)
+ where TOperation : struct, ISymbolOperation
{
if (colors.IsEmpty)
{
return;
}
- this.WriteLiteral(colors[0], bitDepth);
+ this.WriteLiteral(colors[0], bitDepth);
if (colors.Length == 1)
{
return;
@@ -2291,12 +2706,12 @@ internal sealed class Av1SymbolEncoder : IDisposable
(int)Av1Math.CeilLog2((uint)(maximumDelta + 1 - minimumDelta)),
minimumBits);
- this.WriteLiteral((uint)(bits - minimumBits), 2);
+ this.WriteLiteral((uint)(bits - minimumBits), 2);
int range = (1 << bitDepth) - colors[0] - minimumDelta;
for (int i = 1; i < colors.Length; i++)
{
int delta = colors[i] - colors[i - 1];
- this.WriteLiteral((uint)(delta - minimumDelta), bits);
+ this.WriteLiteral((uint)(delta - minimumDelta), bits);
range -= delta;
bits = Math.Min(bits, (int)Av1Math.CeilLog2((uint)range));
}
@@ -2335,10 +2750,14 @@ internal sealed class Av1SymbolEncoder : IDisposable
}
///
- /// Emits coefficient syntax and reports no estimated rate.
+ /// Emits symbols and literals and reports no estimated rate.
///
- private readonly struct CoefficientWriteOperation : ICoefficientSymbolOperation
+ public readonly struct SymbolWriteOperation : ISymbolOperation
{
+ ///
+ public static bool WritesOutput => true;
+
+ ///
public static int ProcessSymbol(
ref Av1SymbolWriter writer,
int symbol,
@@ -2348,6 +2767,18 @@ internal sealed class Av1SymbolEncoder : IDisposable
return 0;
}
+ ///
+ public static int ProcessSymbol(ref Av1SymbolWriter writer, bool symbol, Av1Distribution distribution)
+ => ProcessSymbol(ref writer, symbol ? 1 : 0, distribution);
+
+ ///
+ public static int ProcessBoolean(ref Av1SymbolWriter writer, bool value, uint frequency)
+ {
+ writer.WriteBoolean(value, frequency);
+ return 0;
+ }
+
+ ///
public static int ProcessLiteral(
ref Av1SymbolWriter writer,
uint value,
@@ -2358,17 +2789,56 @@ internal sealed class Av1SymbolEncoder : IDisposable
}
}
+ ///
+ /// Updates adaptive probabilities without emitting symbols or literals.
+ ///
+ public readonly struct SymbolUpdateOperation : ISymbolOperation
+ {
+ ///
+ public static bool WritesOutput => false;
+
+ ///
+ public static int ProcessSymbol(
+ ref Av1SymbolWriter writer,
+ int symbol,
+ Av1Distribution distribution)
+ {
+ writer.UpdateSymbol(symbol, distribution);
+ return 0;
+ }
+
+ ///
+ public static int ProcessSymbol(ref Av1SymbolWriter writer, bool symbol, Av1Distribution distribution)
+ => ProcessSymbol(ref writer, symbol ? 1 : 0, distribution);
+
+ ///
+ public static int ProcessBoolean(ref Av1SymbolWriter writer, bool value, uint frequency)
+ => 0;
+
+ ///
+ public static int ProcessLiteral(ref Av1SymbolWriter writer, uint value, int bitCount)
+ => 0;
+ }
+
///
/// Measures coefficient syntax against the live tile distributions without changing them.
///
- private readonly struct CoefficientCostOperation : ICoefficientSymbolOperation
+ private readonly struct CoefficientCostOperation : ISymbolOperation
{
+ public static bool WritesOutput => false;
+
public static int ProcessSymbol(
ref Av1SymbolWriter writer,
int symbol,
Av1Distribution distribution)
=> Av1ProbabilityCost.GetSymbolCost(distribution, symbol);
+ public static int ProcessSymbol(ref Av1SymbolWriter writer, bool symbol, Av1Distribution distribution)
+ => ProcessSymbol(ref writer, symbol ? 1 : 0, distribution);
+
+ public static int ProcessBoolean(ref Av1SymbolWriter writer, bool value, uint frequency)
+ => Av1ProbabilityCost.GetSymbolCost((int)(value ? frequency : Av1Distribution.ProbabilityTop - frequency));
+
public static int ProcessLiteral(
ref Av1SymbolWriter writer,
uint value,
@@ -2379,14 +2849,17 @@ internal sealed class Av1SymbolEncoder : IDisposable
///
/// Emits palette-map syntax and reports no estimated rate.
///
- private readonly struct PaletteColorMapWriteOperation : IPaletteColorMapOperation
+ private readonly struct PaletteColorMapWriteOperation : IPaletteColorMapOperation
+ where TOperation : struct, ISymbolOperation
{
+ public static bool RetainsTokens => false;
+
public static int ProcessFirstIndex(
Av1SymbolEncoder encoder,
int paletteSize,
int colorIndex)
{
- encoder.WriteUniform(paletteSize, colorIndex);
+ encoder.WriteUniform(paletteSize, colorIndex);
return 0;
}
@@ -2397,7 +2870,7 @@ internal sealed class Av1SymbolEncoder : IDisposable
int colorContext,
int colorOrderIndex)
{
- encoder.WritePaletteColorIndex(
+ encoder.WritePaletteColorIndex(
colorOrderIndex,
paletteSize,
colorContext,
@@ -2407,11 +2880,35 @@ internal sealed class Av1SymbolEncoder : IDisposable
}
}
+ ///
+ /// Retains color tokens while adapting the selected palette distributions.
+ ///
+ private readonly struct PaletteColorMapTokenOperation : IPaletteColorMapOperation
+ {
+ public static bool RetainsTokens => true;
+
+ public static int ProcessFirstIndex(Av1SymbolEncoder encoder, int paletteSize, int colorIndex)
+ => 0;
+
+ public static int ProcessColorIndex(
+ Av1SymbolEncoder encoder,
+ int paletteSize,
+ Av1PlaneType planeType,
+ int colorContext,
+ int colorOrderIndex)
+ {
+ encoder.WritePaletteColorIndex(colorOrderIndex, paletteSize, colorContext, planeType);
+ return 0;
+ }
+ }
+
///
/// Measures palette-map syntax against the live tile distributions without changing them.
///
private readonly struct PaletteColorMapCostOperation : IPaletteColorMapOperation
{
+ public static bool RetainsTokens => false;
+
public static int ProcessFirstIndex(
Av1SymbolEncoder encoder,
int paletteSize,
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs
index 6b26d84d2e..bb35f564d1 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs
@@ -128,8 +128,16 @@ internal sealed class Av1SymbolWriter : IDisposable
DebugGuard.IsTrue(distribution[distribution.NumberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero.");
this.EncodeIntegerQ15(symbol, distribution);
+ this.UpdateSymbol(symbol, distribution);
+ }
- // disable_cdf_update freezes every tile distribution while leaving range encoding unchanged.
+ ///
+ /// Adapts a symbol distribution when probability updates are enabled, without emitting range-coded data.
+ ///
+ /// The zero-based symbol.
+ /// The inverse cumulative distribution for the symbol alphabet.
+ public void UpdateSymbol(int symbol, Av1Distribution distribution)
+ {
if (this.updateCdf)
{
distribution.Update(symbol);
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs
index b9187859a9..fe7710b630 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs
@@ -165,8 +165,8 @@ public class Av1EntropyTests
[InlineData(8, 6144)]
public void SymbolCostUsesRangeCoderMinimumProbability(uint probability, int expected)
{
- // A middle interval can collapse during adaptation. Libaom cost.c floors its mass at EC_MIN_PROB=4,
- // giving 13 * 512 rate units at and below that floor, while mass 8 costs 12 * 512 units.
+ // Adaptation can collapse a symbol's probability interval. A minimum mass of 4 keeps its estimated
+ // cost finite: 13 bits at 512 rate units per bit, compared with 12 bits for a mass of 8.
Av1Distribution distribution = new(16384, 16384 + probability);
Assert.Equal(expected, Av1ProbabilityCost.GetSymbolCost(distribution, 1));
Assert.Equal(expected, Av1ProbabilityCost.GetSymbolCost((int)probability));
@@ -1792,6 +1792,63 @@ public class Av1EntropyTests
decoder.ValidateTrailingBits();
}
+ [Theory]
+ [InlineData(true, (int)Av1PlaneType.Y)]
+ [InlineData(false, (int)Av1PlaneType.Y)]
+ [InlineData(true, (int)Av1PlaneType.Uv)]
+ [InlineData(false, (int)Av1PlaneType.Uv)]
+ public void PaletteTokensSurviveMapReuseWithoutWritingAnalysisBytes(bool updateCdf, int planeTypeValue)
+ {
+ const int Rows = 5;
+ const int Columns = 7;
+ const int PaletteSize = 7;
+ Av1PlaneType planeType = (Av1PlaneType)planeTypeValue;
+ Configuration configuration = Configuration.Default;
+ using Buffer2D source = configuration.MemoryAllocator.Allocate2D(9, 6);
+ Buffer2DRegion region = new(source);
+ Span tokens = stackalloc byte[Rows * Columns];
+ for (int row = 0; row < Rows; row++)
+ {
+ Span samples = source.DangerousGetRowSpan(row);
+ for (int column = 0; column < Columns; column++)
+ {
+ samples[column] = (byte)(((row * 3) + (column * 5)) % PaletteSize);
+ }
+ }
+
+ using Av1SymbolEncoder analysis = new(configuration, 128, BaseQIndex, updateCdf);
+ using Av1SymbolEncoder immediate = new(configuration, 128, BaseQIndex, updateCdf);
+ using Av1SymbolEncoder packing = new(configuration, 128, BaseQIndex, updateCdf);
+ using Av1SymbolEncoder empty = new(configuration, 128, BaseQIndex, updateCdf);
+ analysis.TokenizePaletteColorMap(PaletteSize, planeType, Rows, Columns, region, tokens);
+ immediate.WritePaletteColorMap(PaletteSize, planeType, Rows, Columns, region);
+
+ for (int context = 0; context < 5; context++)
+ {
+ for (int color = 0; color < PaletteSize; color++)
+ {
+ Assert.Equal(
+ immediate.GetPaletteColorIndexCost(color, PaletteSize, context, planeType),
+ analysis.GetPaletteColorIndexCost(color, PaletteSize, context, planeType));
+ }
+ }
+
+ // The prediction workspace is reusable immediately after analysis. Poison every row, including its
+ // padding, so final packing must consume retained tokens rather than consult the old color map.
+ for (int row = 0; row < source.Height; row++)
+ {
+ source.DangerousGetRowSpan(row).Fill(byte.MaxValue);
+ }
+
+ packing.WritePaletteTokens(PaletteSize, planeType, tokens);
+ using IMemoryOwner analyzedBytes = analysis.Exit();
+ using IMemoryOwner emptyBytes = empty.Exit();
+ using IMemoryOwner immediateBytes = immediate.Exit();
+ using IMemoryOwner packedBytes = packing.Exit();
+ Assert.True(emptyBytes.GetSpan().SequenceEqual(analyzedBytes.GetSpan()));
+ Assert.True(immediateBytes.GetSpan().SequenceEqual(packedBytes.GetSpan()));
+ }
+
[Fact]
public void PaletteColorMapCostDoesNotAllocateAfterEntropyInitialization()
{