Browse Source

Choose cheaper AV1 empty-block syntax

pull/2633/head
James Jackson-South 1 month ago
parent
commit
dc3127087e
  1. 3
      HEIF_IMPLEMENTATION_PLAN.md
  2. 96
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs
  3. 42
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
  4. 33
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs

3
HEIF_IMPLEMENTATION_PLAN.md

File diff suppressed because one or more lines are too long

96
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs

@ -199,10 +199,24 @@ internal static partial class Av1IntraSuperblockEncoder
block.FilterIntraMode = filterIntraMode;
this.codedAreaLuma += LumaTransformSize.GetSize2d();
bool skipTransform = lumaState.EndOfBlock == 0;
bool lumaTransformEmpty = lumaState.EndOfBlock == 0;
if (this.source.IsMonochrome)
{
modeInfo.Block.Skip = skipTransform;
modeInfo.Block.Skip = lumaTransformEmpty &&
Av1TileWriter.ShouldSkipCoefficients(
writer,
Av1TileWriter.GetSkipContext(macroBlock),
this.GetEmptyTransformRate(
writer,
this.picture.LuminanceDcSignLevelCoefficientNeighbors[tileIndex],
Av1ComponentType.Luminance,
blockOrigin,
BlockSize,
LumaTransformSize,
lumaState.TransformType,
modeInfo.Block.Mode,
block.FilterIntraMode));
return;
}
@ -247,11 +261,85 @@ internal static partial class Av1IntraSuperblockEncoder
block.PredictionUnit.ChromaFromLumaIndex = chromaFromLumaIndex;
block.PredictionUnit.ChromaFromLumaSigns = chromaFromLumaSigns;
// A block-level skip suppresses every coefficient symbol, so all coded planes must be empty.
modeInfo.Block.Skip = skipTransform && blueState.EndOfBlock == 0 && redState.EndOfBlock == 0;
bool allTransformsEmpty = lumaTransformEmpty && blueState.EndOfBlock == 0 && redState.EndOfBlock == 0;
if (allTransformsEmpty)
{
Av1BlockSize chromaBlockSize = BlockSize.GetSubsampled(
colorConfig.SubSamplingX,
colorConfig.SubSamplingY);
int emptyTransformRate = this.GetEmptyTransformRate(
writer,
this.picture.LuminanceDcSignLevelCoefficientNeighbors[tileIndex],
Av1ComponentType.Luminance,
blockOrigin,
BlockSize,
LumaTransformSize,
lumaState.TransformType,
modeInfo.Block.Mode,
block.FilterIntraMode);
emptyTransformRate += this.GetEmptyTransformRate(
writer,
this.picture.CbDcSignLevelCoefficientNeighbors[tileIndex],
Av1ComponentType.Chroma,
chromaOrigin,
chromaBlockSize,
chromaTransformSize,
blueState.TransformType,
modeInfo.Block.Mode,
Av1FilterIntraMode.AllFilterIntraModes);
emptyTransformRate += this.GetEmptyTransformRate(
writer,
this.picture.CrDcSignLevelCoefficientNeighbors[tileIndex],
Av1ComponentType.Chroma,
chromaOrigin,
chromaBlockSize,
chromaTransformSize,
redState.TransformType,
modeInfo.Block.Mode,
Av1FilterIntraMode.AllFilterIntraModes);
modeInfo.Block.Skip = Av1TileWriter.ShouldSkipCoefficients(
writer,
Av1TileWriter.GetSkipContext(macroBlock),
emptyTransformRate);
}
this.codedAreaChroma += chromaTransformSize.GetSize2d();
}
private int GetEmptyTransformRate(
Av1SymbolEncoder writer,
Av1NeighborArrayUnit<byte> coefficientNeighbors,
Av1ComponentType componentType,
Point blockOrigin,
Av1BlockSize blockSize,
Av1TransformSize transformSize,
Av1TransformType transformType,
Av1PredictionMode lumaMode,
Av1FilterIntraMode filterIntraMode)
{
Av1TransformBlockContext blockContext = Av1TileWriter.GetTransformBlockContexts(
componentType,
coefficientNeighbors,
blockOrigin,
blockSize,
transformSize);
return writer.GetCoefficientCost(
transformSize,
transformType,
lumaMode,
ReadOnlySpan<int>.Empty,
componentType,
blockContext,
0,
this.picture.Parent.FrameHeader.UseReducedTransformSet,
filterIntraMode);
}
private Av1PredictionMode SelectLumaMode(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,

42
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs

@ -1871,6 +1871,42 @@ internal partial class Av1TileWriter
return (prev_ul == prev_u) ? prev_u : prev_l;
}
/// <summary>
/// Gets the block skip context from the available above and left modes.
/// </summary>
/// <param name="macroBlock">The reusable macroblock edge and neighbor state.</param>
/// <returns>The sum of the available above and left skip states.</returns>
public static int GetSkipContext(Av1MacroBlockD macroBlock)
{
bool aboveSkipped = macroBlock.IsUpAvailable &&
macroBlock.GetRelativeModeInfo(-macroBlock.ModeInfoStride).Block.Skip;
bool leftSkipped = macroBlock.IsLeftAvailable && macroBlock.GetRelativeModeInfo(-1).Block.Skip;
return (aboveSkipped ? 1 : 0) + (leftSkipped ? 1 : 0);
}
/// <summary>
/// Selects block skip when it is cheaper than retaining empty transform syntax.
/// </summary>
/// <param name="writer">The live tile symbol encoder.</param>
/// <param name="skipContext">The neighboring block skip context.</param>
/// <param name="emptyTransformRate">The complete coefficient rate for the empty transforms.</param>
/// <returns>
/// <see langword="true"/> when block skip has a strictly lower rate; otherwise, <see langword="false"/>.
/// </returns>
public static bool ShouldSkipCoefficients(
Av1SymbolEncoder writer,
int skipContext,
int emptyTransformRate)
{
int skipRate = writer.GetSkipCost(true, skipContext);
int nonSkipRate = writer.GetSkipCost(false, skipContext) + emptyTransformRate;
// Current libaom keeps intra blocks non-skipped. Empty transforms make both choices
// decoder-identical, so select skip only when its complete live rate is strictly lower.
return skipRate < nonSkipRate;
}
/// <summary>
/// Writes the block skip flag using the sum of available above and left skip states as its context.
/// </summary>
@ -1878,9 +1914,5 @@ internal partial class Av1TileWriter
/// <param name="macroBlock">The reusable macroblock edge and neighbor state.</param>
/// <param name="skip">The skip value to write.</param>
public static void EncodeSkipCoefficients(Av1SymbolEncoder writer, Av1MacroBlockD macroBlock, bool skip)
{
int aboveSkip = macroBlock.IsUpAvailable && macroBlock.GetRelativeModeInfo(-macroBlock.ModeInfoStride).Block.Skip ? 1 : 0;
int leftSkip = macroBlock.IsLeftAvailable && macroBlock.GetRelativeModeInfo(-1).Block.Skip ? 1 : 0;
writer.WriteSkip(skip, aboveSkip + leftSkip);
}
=> writer.WriteSkip(skip, GetSkipContext(macroBlock));
}

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

@ -142,6 +142,39 @@ public class Av1EntropyTests
encoder.GetTransformBlockSkipCost(true, TransformSize, SkipContext));
}
[Fact]
public void BlockSkipDecisionUsesAdaptedRatesForEmptyTransforms()
{
const int QIndex = 73;
const int BlockSkipContext = 2;
const int TransformSkipContext = 0;
const Av1TransformSize TransformSize = Av1TransformSize.Size8x8;
using Av1SymbolEncoder encoder = new(Configuration.Default, 256, QIndex);
int emptyTransformRate = encoder.GetTransformBlockSkipCost(
true,
TransformSize,
TransformSkipContext);
Assert.True(
Av1TileWriter.ShouldSkipCoefficients(
encoder,
BlockSkipContext,
emptyTransformRate));
// Repeated non-skip symbols make another block skip more expensive while the empty-transform
// rate remains unchanged, proving the decision reads the adapted live distribution.
for (int index = 0; index < 256; index++)
{
encoder.WriteSkip(false, BlockSkipContext);
}
Assert.False(
Av1TileWriter.ShouldSkipCoefficients(
encoder,
BlockSkipContext,
emptyTransformRate));
}
[Theory]
[InlineData(-16, 16)]
[InlineData(0, 8)]

Loading…
Cancel
Save