Browse Source

Separate AV1 probability adaptation from syntax packing

pull/2633/head
James Jackson-South 4 weeks ago
parent
commit
d8f6a1de31
  1. 22
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1MotionVectorContext.cs
  2. 807
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
  3. 10
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs
  4. 61
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs

22
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1MotionVectorContext.cs

@ -107,7 +107,17 @@ internal sealed class Av1MotionVectorContext
/// <param name="reference">The spatially derived reference vector.</param> /// <param name="reference">The spatially derived reference vector.</param>
/// <param name="precision">The fractional precision selected by the frame header.</param> /// <param name="precision">The fractional precision selected by the frame header.</param>
public void Write(Av1SymbolWriter writer, Av1MotionVector value, Av1MotionVector reference, Av1MotionVectorPrecision precision) public void Write(Av1SymbolWriter writer, Av1MotionVector value, Av1MotionVector reference, Av1MotionVectorPrecision precision)
=> _ = this.Process<MotionVectorWriteOperation>(writer, value, reference, precision); => this.Write<Av1SymbolEncoder.SymbolWriteOperation>(writer, value, reference, precision);
/// <inheritdoc cref="Write(Av1SymbolWriter, Av1MotionVector, Av1MotionVector, Av1MotionVectorPrecision)"/>
/// <typeparam name="TOperation">The operation applied to each motion-vector symbol.</typeparam>
public void Write<TOperation>(
Av1SymbolWriter writer,
Av1MotionVector value,
Av1MotionVector reference,
Av1MotionVectorPrecision precision)
where TOperation : struct, Av1SymbolEncoder.ISymbolOperation
=> _ = this.Process<MotionVectorWriteOperation<TOperation>>(writer, value, reference, precision);
/// <summary> /// <summary>
/// Measures a motion-vector delta against the live distributions without changing them. /// Measures a motion-vector delta against the live distributions without changing them.
@ -150,14 +160,12 @@ internal sealed class Av1MotionVectorContext
/// <summary> /// <summary>
/// Emits motion-vector syntax and reports no estimated rate. /// Emits motion-vector syntax and reports no estimated rate.
/// </summary> /// </summary>
private readonly struct MotionVectorWriteOperation : IMotionVectorSymbolOperation private readonly struct MotionVectorWriteOperation<TOperation> : IMotionVectorSymbolOperation
where TOperation : struct, Av1SymbolEncoder.ISymbolOperation
{ {
/// <inheritdoc/> /// <inheritdoc/>
public static int ProcessSymbol(Av1SymbolWriter writer, int symbol, Av1Distribution distribution) public static int ProcessSymbol(Av1SymbolWriter writer, int symbol, Av1Distribution distribution)
{ => TOperation.ProcessSymbol(ref writer, symbol, distribution);
writer.WriteSymbol(symbol, distribution);
return 0;
}
} }
/// <summary> /// <summary>
@ -346,7 +354,7 @@ internal sealed class Av1MotionVectorContext
/// <param name="value">The nonzero component in one-eighth-sample units.</param> /// <param name="value">The nonzero component in one-eighth-sample units.</param>
/// <param name="precision">The fractional precision selected by the frame header.</param> /// <param name="precision">The fractional precision selected by the frame header.</param>
public void Write(Av1SymbolWriter writer, int value, Av1MotionVectorPrecision precision) public void Write(Av1SymbolWriter writer, int value, Av1MotionVectorPrecision precision)
=> _ = this.Process<MotionVectorWriteOperation>(writer, value, precision); => _ = this.Process<MotionVectorWriteOperation<Av1SymbolEncoder.SymbolWriteOperation>>(writer, value, precision);
/// <summary> /// <summary>
/// Processes one nonzero signed component through the shared motion-vector symbol operation. /// Processes one nonzero signed component through the shared motion-vector symbol operation.

807
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs

File diff suppressed because it is too large

10
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."); DebugGuard.IsTrue(distribution[distribution.NumberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero.");
this.EncodeIntegerQ15(symbol, distribution); this.EncodeIntegerQ15(symbol, distribution);
this.UpdateSymbol(symbol, distribution);
}
// disable_cdf_update freezes every tile distribution while leaving range encoding unchanged. /// <summary>
/// Adapts a symbol distribution when probability updates are enabled, without emitting range-coded data.
/// </summary>
/// <param name="symbol">The zero-based symbol.</param>
/// <param name="distribution">The inverse cumulative distribution for the symbol alphabet.</param>
public void UpdateSymbol(int symbol, Av1Distribution distribution)
{
if (this.updateCdf) if (this.updateCdf)
{ {
distribution.Update(symbol); distribution.Update(symbol);

61
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs

@ -165,8 +165,8 @@ public class Av1EntropyTests
[InlineData(8, 6144)] [InlineData(8, 6144)]
public void SymbolCostUsesRangeCoderMinimumProbability(uint probability, int expected) 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, // Adaptation can collapse a symbol's probability interval. A minimum mass of 4 keeps its estimated
// giving 13 * 512 rate units at and below that floor, while mass 8 costs 12 * 512 units. // 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); Av1Distribution distribution = new(16384, 16384 + probability);
Assert.Equal(expected, Av1ProbabilityCost.GetSymbolCost(distribution, 1)); Assert.Equal(expected, Av1ProbabilityCost.GetSymbolCost(distribution, 1));
Assert.Equal(expected, Av1ProbabilityCost.GetSymbolCost((int)probability)); Assert.Equal(expected, Av1ProbabilityCost.GetSymbolCost((int)probability));
@ -1792,6 +1792,63 @@ public class Av1EntropyTests
decoder.ValidateTrailingBits(); 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<byte> source = configuration.MemoryAllocator.Allocate2D<byte>(9, 6);
Buffer2DRegion<byte> region = new(source);
Span<byte> tokens = stackalloc byte[Rows * Columns];
for (int row = 0; row < Rows; row++)
{
Span<byte> 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<byte> analyzedBytes = analysis.Exit();
using IMemoryOwner<byte> emptyBytes = empty.Exit();
using IMemoryOwner<byte> immediateBytes = immediate.Exit();
using IMemoryOwner<byte> packedBytes = packing.Exit();
Assert.True(emptyBytes.GetSpan().SequenceEqual(analyzedBytes.GetSpan()));
Assert.True(immediateBytes.GetSpan().SequenceEqual(packedBytes.GetSpan()));
}
[Fact] [Fact]
public void PaletteColorMapCostDoesNotAllocateAfterEntropyInitialization() public void PaletteColorMapCostDoesNotAllocateAfterEntropyInitialization()
{ {

Loading…
Cancel
Save