Browse Source

Preserve AV1 rate and distortion through candidate aggregation

pull/2633/head
James Jackson-South 4 weeks ago
parent
commit
fff06700db
  1. 29
      HEIF_IMPLEMENTATION_PLAN.md
  2. 62
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1RateDistortionStatistics.cs
  3. 40
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaModeDecision.cs
  4. 8
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaPaletteModeDecision.cs
  5. 157
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs
  6. 24
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.PaletteModeDecision.cs
  7. 64
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs
  8. 23
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs
  9. 165
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs

29
HEIF_IMPLEMENTATION_PLAN.md

@ -370,6 +370,35 @@ Film-grain decoder source comparison after `ef8b1a823`:
passing managed tests. This is bounded same-bitstream decoder evidence, not complete conformance or encoder parity.
No benchmark ran, and no fixture, native integration, or generated comparison output was added to the repository.
RD accumulation correction after `4c7880d`:
- Numerical defect: before this correction, `Av1IntraSuperblockEncoder.ModeDecision.cs:349-381` added
rounded child costs, `:727-731` added rounded luma/chroma costs, and `:1193-1212` added separately rounded
block syntax. Reference `av1/encoder/rd.h:32-34,208-233` keeps raw rate and distortion; rectangle and split
accumulation in `partition_search.c:3487-3504,4605-4607` rounds the combined rate. For multiplier 128,
two rates of 2 cost 1 jointly but cost 2 when rounded separately; two rates of 1 show the opposite error.
- `Av1RateDistortionStatistics.cs:9-61` now carries raw rate, distortion, and the comparison cost through
spatial, tiled, filter-intra, palette, CfL, IBC, and inter winners. Mode/transform comparisons retain their
existing strict tie rules and scalar bounds. Bounded-out split candidates retain an invalid sentinel;
only valid selected statistics are accumulated. Existing per-transform raw accumulation is preserved.
- Partition evaluation now combines those statistics in `Av1IntraSuperblockEncoder.ModeDecision.cs:336-390`;
ordinary block syntax and luma/chroma aggregation use the same retained raw inputs. This adds value state,
not a buffer or allocation. It does not implement native partition pruning, mode order, cost refresh,
quantization stages, reference control, or adaptive rate multipliers. No performance improvement is claimed.
- Seven focused regressions pass in Release .NET 11 through serialized Visual Studio VSTest
(`rd-statistics-focused.trx`, 2.6557 seconds). Four production block cases independently recount selected
syntax and pixel-domain SSE for monochrome/color and zero/nonzero residuals. Three fixed arithmetic cases
cover both rounding directions and 64-bit distortion.
- After the final C# edit, the Release .NET 11 build completed with zero errors and 1,009 existing warnings;
Roslynk reported zero compiler errors. Serialized Visual Studio VSTest passed 2,259 entropy,
intra-superblock, HEIF encoder, and reconstruction-conformance cases in 2.1328 minutes
(`rd-statistics-final.trx`), then 132 encoder-frame cases in 18.7250 seconds (`rd-statistics-frames.trx`).
The seven new cases are included in those totals. Tests encoding the existing effort policy only verify
current behavior; their success does not validate that policy against libaom.
- Optimized libaom decoding of the regenerated eight partition, 23 palette, and twelve color-sequence
streams matches all 38,973 samples exactly: maximum error 0, zero samples exceeding one. This is
same-bitstream reconstruction evidence. Separate-encoder acceptance remains unmet; no benchmark ran.
Range-writer output-capacity correction, verified after `93aba785f`:
- `Av1SymbolWriter.cs:213-214,339` before correction sliced a fixed initial allocation for finalization

62
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1RateDistortionStatistics.cs

@ -0,0 +1,62 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
/// <summary>
/// Holds the rate, distortion, and rounded cost of an encoder candidate.
/// </summary>
internal struct Av1RateDistortionStatistics
{
/// <summary>
/// Initializes a new instance of the <see cref="Av1RateDistortionStatistics"/> struct.
/// </summary>
/// <param name="rateMultiplier">The rate multiplier for the current block.</param>
/// <param name="rate">The estimated syntax rate in 1/512-bit units.</param>
/// <param name="distortion">The candidate distortion.</param>
public Av1RateDistortionStatistics(int rateMultiplier, int rate, long distortion)
{
this.Rate = rate;
this.Distortion = distortion;
this.Cost = Av1RateDistortion.GetCost(rateMultiplier, rate, distortion);
}
/// <summary>
/// Gets the sentinel for a candidate that cannot win a cost comparison.
/// </summary>
public static Av1RateDistortionStatistics Invalid => new()
{
Rate = int.MaxValue,
Distortion = long.MaxValue,
Cost = long.MaxValue
};
/// <summary>
/// Gets the estimated syntax rate in 1/512-bit units.
/// </summary>
public int Rate { get; private set; }
/// <summary>
/// Gets the candidate distortion.
/// </summary>
public long Distortion { get; private set; }
/// <summary>
/// Gets the rounded rate-distortion cost.
/// </summary>
public long Cost { get; private set; }
/// <summary>
/// Adds a valid candidate's rate and distortion and updates the combined cost.
/// </summary>
/// <param name="rateMultiplier">The rate multiplier for the combined candidate.</param>
/// <param name="other">The valid candidate to add.</param>
public void Add(int rateMultiplier, in Av1RateDistortionStatistics other)
{
// Round the combined rate only once. Adding the already rounded child costs can change
// partition and inter/intra decisions even when both children have the same reconstruction.
this.Rate += other.Rate;
this.Distortion += other.Distortion;
this.Cost = Av1RateDistortion.GetCost(rateMultiplier, this.Rate, this.Distortion);
}
}

40
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaModeDecision.cs

@ -58,7 +58,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta,
out byte selectedChromaFromLumaIndex,
out sbyte selectedChromaFromLumaSigns,
out long selectedCost)
out Av1RateDistortionStatistics selectedStatistics)
{
Av1EncoderModeDecisionWorkspace<TSample> workspace =
this.blockWorkspace.GetModeDecisionWorkspace<TSample>();
@ -94,7 +94,7 @@ internal static partial class Av1IntraSuperblockEncoder
out selectedAngleDelta,
out selectedChromaFromLumaIndex,
out selectedChromaFromLumaSigns,
out selectedCost);
out selectedStatistics);
}
int modeInfoRow = lumaOrigin.Y >> Av1Constants.ModeInfoSizeLog2;
@ -201,7 +201,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<TSample> candidateRedReconstruction = workspace.GetCandidateReconstruction(1);
Span<int> candidateBlueCoefficients = workspace.GetCandidateCoefficients(0);
Span<int> candidateRedCoefficients = workspace.GetCandidateCoefficients(1);
long bestCost = long.MaxValue;
Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1ChromaPredictionMode bestMode = Av1ChromaPredictionMode.DC;
selectedAngleDelta = 0;
selectedChromaFromLumaIndex = 0;
@ -249,7 +249,7 @@ internal static partial class Av1IntraSuperblockEncoder
// A chroma mode and angle are shared by U and V, so neither plane can replace the
// retained result independently. Their complete rate and distortion compete jointly.
long candidateCost = this.GetChromaCandidateCost(
Av1RateDistortionStatistics candidateStatistics = this.GetChromaCandidateCost(
writer,
modeInfo,
lumaMode,
@ -277,7 +277,7 @@ internal static partial class Av1IntraSuperblockEncoder
ref candidateBlueState,
ref candidateRedState);
if (candidateCost < bestCost)
if (candidateStatistics.Cost < bestStatistics.Cost)
{
CopyCandidate(
candidateBlueReconstruction,
@ -299,7 +299,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateRedState,
ref retainedRedStates[0]);
bestCost = candidateCost;
bestStatistics = candidateStatistics;
bestMode = chromaMode;
selectedAngleDelta = angleDelta;
}
@ -430,15 +430,15 @@ internal static partial class Av1IntraSuperblockEncoder
+ writer.GetChromaFromLumaCost(packedIndex, jointSign);
long distortion = blueDistortions[blueCandidateIndex] + redDistortions[redCandidateIndex];
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
bool winsSearchOrderTie = candidateCost == bestCost
Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
bool winsSearchOrderTie = candidateStatistics.Cost == bestStatistics.Cost
&& !chromaFromLumaSelected
&& bestMode != Av1ChromaPredictionMode.DC;
// CfL follows DC and precedes every other chroma mode in the reference search order.
if (candidateCost < bestCost || winsSearchOrderTie)
if (candidateStatistics.Cost < bestStatistics.Cost || winsSearchOrderTie)
{
bestCost = candidateCost;
bestStatistics = candidateStatistics;
bestMode = Av1ChromaPredictionMode.ChromaFromLuma;
selectedAngleDelta = 0;
selectedBlueCandidateIndex = blueCandidateIndex;
@ -532,7 +532,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedRedCoefficients,
ref retainedBlueStates[0],
ref retainedRedStates[0],
ref bestCost,
ref bestStatistics,
ref paletteInfo))
{
bestMode = Av1ChromaPredictionMode.DC;
@ -541,7 +541,7 @@ internal static partial class Av1IntraSuperblockEncoder
selectedChromaFromLumaSigns = 0;
}
selectedCost = bestCost;
selectedStatistics = bestStatistics;
return bestMode;
}
@ -564,7 +564,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta,
out byte selectedChromaFromLumaIndex,
out sbyte selectedChromaFromLumaSigns,
out long selectedCost)
out Av1RateDistortionStatistics selectedStatistics)
{
Av1EncoderModeDecisionWorkspace<TSample> workspace =
this.blockWorkspace.GetModeDecisionWorkspace<TSample>();
@ -650,7 +650,7 @@ internal static partial class Av1IntraSuperblockEncoder
_ => baseModeCount
};
long bestCost = long.MaxValue;
Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1ChromaPredictionMode bestMode = Av1ChromaPredictionMode.DC;
selectedAngleDelta = 0;
selectedChromaFromLumaIndex = 0;
@ -745,8 +745,8 @@ internal static partial class Av1IntraSuperblockEncoder
rate += paletteDisabledCost;
}
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
if (candidateCost < bestCost)
Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
if (candidateStatistics.Cost < bestStatistics.Cost)
{
CopyTiledCandidate(
candidateBlueReconstruction,
@ -772,13 +772,13 @@ internal static partial class Av1IntraSuperblockEncoder
retainedRedCoefficients,
retainedRedStates);
bestCost = candidateCost;
bestStatistics = candidateStatistics;
bestMode = chromaMode;
selectedAngleDelta = angleDelta;
}
}
selectedCost = bestCost;
selectedStatistics = bestStatistics;
return bestMode;
}
@ -1083,7 +1083,7 @@ internal static partial class Av1IntraSuperblockEncoder
&& modeInfo.UvMode is Av1ChromaPredictionMode.Smooth or Av1ChromaPredictionMode.SmoothVertical or Av1ChromaPredictionMode.SmoothHorizontal;
}
private long GetChromaCandidateCost(
private Av1RateDistortionStatistics GetChromaCandidateCost(
Av1SymbolEncoder writer,
Av1MacroBlockModeInfo modeInfo,
Av1PredictionMode lumaMode,
@ -1209,7 +1209,7 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false);
return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
return new(this.rateMultiplier, rate, distortion);
}
/// <summary>

8
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaPaletteModeDecision.cs

@ -38,7 +38,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<int> retainedRedCoefficients,
ref Av1EncoderTransformBlockState retainedBlueState,
ref Av1EncoderTransformBlockState retainedRedState,
ref long bestCost,
ref Av1RateDistortionStatistics bestStatistics,
ref Av1EncoderPaletteInfo paletteInfo)
{
const Av1BlockSize BlockSize = Av1BlockSize.Block8x8;
@ -336,8 +336,8 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false);
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
if (candidateCost < bestCost)
Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
if (candidateStatistics.Cost < bestStatistics.Cost)
{
// Every following palette size overwrites the shared maps and candidate spans, so a
// global improvement must retain reconstruction, coefficients, colors, and indices together.
@ -372,7 +372,7 @@ internal static partial class Av1IntraSuperblockEncoder
paletteInfo.PaletteSizes[1] = (byte)paletteSize;
paletteInfo.SetColors(Av1Plane.U, bluePaletteColors);
paletteInfo.SetColors(Av1Plane.V, redPaletteColors);
bestCost = candidateCost;
bestStatistics = candidateStatistics;
paletteSelected = true;
}
}

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

@ -139,7 +139,6 @@ internal static partial class Av1IntraSuperblockEncoder
private readonly int effort;
private int codedAreaLuma;
private int codedAreaChroma;
private long selectedBlockCost;
/// <summary>
/// Initializes a new instance of the <see cref="ModeDecision{TSample, TOperator}"/> struct.
@ -178,9 +177,14 @@ internal static partial class Av1IntraSuperblockEncoder
this.effort = effort;
this.codedAreaLuma = 0;
this.codedAreaChroma = 0;
this.selectedBlockCost = 0;
this.SelectedBlockStatistics = default;
}
/// <summary>
/// Gets the statistics of the most recently encoded block.
/// </summary>
public Av1RateDistortionStatistics SelectedBlockStatistics { get; private set; }
/// <inheritdoc/>
public Av1PartitionType SelectPartition(
Av1SymbolEncoder writer,
@ -254,7 +258,7 @@ internal static partial class Av1IntraSuperblockEncoder
int savedLumaArea = this.codedAreaLuma;
int savedChromaArea = this.codedAreaChroma;
this.SavePartitionTrialContexts(blockOrigin, tileIndex, blockSize);
long bestCost = long.MaxValue;
Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1PartitionType selectedPartition = Av1PartitionType.None;
ReadOnlySpan<Av1PartitionType> searchOrder = PartitionSearchOrder;
int candidateCount = blockSize == Av1BlockSize.Block8x8 ? 4 : searchOrder.Length;
@ -266,7 +270,7 @@ internal static partial class Av1IntraSuperblockEncoder
continue;
}
long candidateCost = this.EvaluatePartitionCandidate(
Av1RateDistortionStatistics candidateStatistics = this.EvaluatePartitionCandidate(
writer,
macroBlock,
blockOrigin,
@ -275,9 +279,9 @@ internal static partial class Av1IntraSuperblockEncoder
partitionType,
publishFinalContexts: false);
if (candidateCost < bestCost)
if (candidateStatistics.Cost < bestStatistics.Cost)
{
bestCost = candidateCost;
bestStatistics = candidateStatistics;
selectedPartition = partitionType;
}
@ -292,7 +296,7 @@ internal static partial class Av1IntraSuperblockEncoder
return selectedPartition;
}
private long EvaluateSelectedPartitionTree(
private Av1RateDistortionStatistics EvaluateSelectedPartitionTree(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
@ -307,7 +311,7 @@ internal static partial class Av1IntraSuperblockEncoder
tileIndex,
blockSize);
long cost = this.EvaluatePartitionCandidate(
Av1RateDistortionStatistics statistics = this.EvaluatePartitionCandidate(
writer,
macroBlock,
blockOrigin,
@ -326,10 +330,10 @@ internal static partial class Av1IntraSuperblockEncoder
selectedPartition);
}
return cost;
return statistics;
}
private long EvaluatePartitionCandidate(
private Av1RateDistortionStatistics EvaluatePartitionCandidate(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
@ -346,7 +350,7 @@ internal static partial class Av1IntraSuperblockEncoder
blockOrigin,
this.picture.PartitionContexts[tileIndex]);
long cost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, 0);
Av1RateDistortionStatistics statistics = new(this.rateMultiplier, rate, 0);
int leafCount = GetPartitionLeafCount(partitionType);
// Child reconstruction and syntax contexts become input to the next child. Publishing only
@ -362,7 +366,7 @@ internal static partial class Av1IntraSuperblockEncoder
out Av1BlockSize leafSize);
bool publishContexts = leafIndex < leafCount - 1 || publishFinalContexts;
cost += partitionType == Av1PartitionType.Split && blockSize > Av1BlockSize.Block8x8
Av1RateDistortionStatistics childStatistics = partitionType == Av1PartitionType.Split && blockSize > Av1BlockSize.Block8x8
? this.EvaluateSelectedPartitionTree(
writer,
macroBlock,
@ -378,9 +382,11 @@ internal static partial class Av1IntraSuperblockEncoder
leafSize,
partitionType == Av1PartitionType.Split ? Av1PartitionType.None : partitionType,
publishContexts);
statistics.Add(this.rateMultiplier, in childStatistics);
}
return cost;
return statistics;
}
private void ResetPartitionTrial(
@ -613,7 +619,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int lumaAngleDelta,
out Av1FilterIntraMode filterIntraMode,
out Av1TransformSize lumaTransformSize,
out long lumaCost);
out Av1RateDistortionStatistics lumaStatistics);
block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Y] = (sbyte)lumaAngleDelta;
block.FilterIntraMode = filterIntraMode;
@ -626,37 +632,37 @@ internal static partial class Av1IntraSuperblockEncoder
bool allowIntraBlockCopy = blockSize == Av1BlockSize.Block8x8 &&
this.picture.Parent.FrameHeader.AllowIntraBlockCopy;
long regularCost = this.GetRegularBlockCost(
Av1RateDistortionStatistics regularStatistics = this.GetRegularBlockCost(
writer,
macroBlock,
lumaCost,
lumaStatistics,
allowIntraBlockCopy);
if (!this.picture.Parent.FrameHeader.IsIntra)
{
this.selectedBlockCost = this.SelectInterPrediction(
this.SelectedBlockStatistics = this.SelectInterPrediction(
writer,
macroBlock,
blockOrigin,
tileIndex,
regularCost,
regularStatistics,
ref modeInfo,
ref block,
ref paletteInfo);
}
else
{
this.selectedBlockCost = allowIntraBlockCopy
this.SelectedBlockStatistics = allowIntraBlockCopy
? this.SelectIntraBlockCopy(
writer,
macroBlock,
blockOrigin,
tileIndex,
regularCost,
regularStatistics,
ref modeInfo,
ref block,
ref paletteInfo)
: regularCost;
: regularStatistics;
}
this.codedAreaLuma += blockSize.GetWidth() * blockSize.GetHeight();
@ -693,7 +699,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<Av1EncoderTransformBlockState> retainedBlueStates = blueTransformBlocks[chromaTransformIndex..];
Span<Av1EncoderTransformBlockState> retainedRedStates = redTransformBlocks[chromaTransformIndex..];
long chromaCost = 0;
Av1RateDistortionStatistics chromaStatistics = default;
if (block.HasChroma)
{
modeInfo.Block.UvMode = this.SelectChromaMode(
@ -714,7 +720,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int chromaAngleDelta,
out byte chromaFromLumaIndex,
out sbyte chromaFromLumaSigns,
out chromaCost);
out chromaStatistics);
block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv] = (sbyte)chromaAngleDelta;
block.PredictionUnit.ChromaFromLumaIndex = chromaFromLumaIndex;
@ -724,37 +730,38 @@ internal static partial class Av1IntraSuperblockEncoder
bool allowColorIntraBlockCopy = blockSize == Av1BlockSize.Block8x8 &&
this.picture.Parent.FrameHeader.AllowIntraBlockCopy;
long regularColorCost = this.GetRegularBlockCost(
lumaStatistics.Add(this.rateMultiplier, in chromaStatistics);
Av1RateDistortionStatistics regularColorStatistics = this.GetRegularBlockCost(
writer,
macroBlock,
lumaCost + chromaCost,
lumaStatistics,
allowColorIntraBlockCopy);
if (!this.picture.Parent.FrameHeader.IsIntra)
{
this.selectedBlockCost = this.SelectInterPrediction(
this.SelectedBlockStatistics = this.SelectInterPrediction(
writer,
macroBlock,
blockOrigin,
tileIndex,
regularColorCost,
regularColorStatistics,
ref modeInfo,
ref block,
ref paletteInfo);
}
else
{
this.selectedBlockCost = allowColorIntraBlockCopy
this.SelectedBlockStatistics = allowColorIntraBlockCopy
? this.SelectIntraBlockCopy(
writer,
macroBlock,
blockOrigin,
tileIndex,
regularColorCost,
regularColorStatistics,
ref modeInfo,
ref block,
ref paletteInfo)
: regularColorCost;
: regularColorStatistics;
}
this.codedAreaLuma += blockSize.GetWidth() * blockSize.GetHeight();
@ -764,7 +771,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
}
private long EvaluatePartitionLeaf(
private Av1RateDistortionStatistics EvaluatePartitionLeaf(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
@ -812,7 +819,7 @@ internal static partial class Av1IntraSuperblockEncoder
paletteInfo);
}
return this.selectedBlockCost;
return this.SelectedBlockStatistics;
}
private void SetBlockGeometry(
@ -1190,10 +1197,10 @@ internal static partial class Av1IntraSuperblockEncoder
offset += left.Length;
}
private long GetRegularBlockCost(
private Av1RateDistortionStatistics GetRegularBlockCost(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
long modeCost,
Av1RateDistortionStatistics modeStatistics,
bool allowIntraBlockCopy)
{
int rateAdjustment = writer.GetSkipCost(false, Av1TileWriter.GetSkipContext(macroBlock));
@ -1208,7 +1215,7 @@ internal static partial class Av1IntraSuperblockEncoder
rateAdjustment += writer.GetUseIntraBlockCopyCost(false);
}
return modeCost + Av1RateDistortion.GetCost(this.rateMultiplier, rateAdjustment, 0);
return new(this.rateMultiplier, modeStatistics.Rate + rateAdjustment, modeStatistics.Distortion);
}
private Av1PredictionMode SelectLumaMode(
@ -1223,7 +1230,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta,
out Av1FilterIntraMode selectedFilterIntraMode,
out Av1TransformSize selectedTransformSize,
out long selectedCost)
out Av1RateDistortionStatistics selectedStatistics)
{
bool codedLossless = this.picture.Parent.FrameHeader.CodedLossless;
Av1TransformSize transformSize = codedLossless
@ -1251,7 +1258,7 @@ internal static partial class Av1IntraSuperblockEncoder
out selectedAngleDelta,
out selectedFilterIntraMode,
out selectedTransformSize,
out selectedCost);
out selectedStatistics);
}
bool hasLeft = macroBlock.IsLeftAvailable;
@ -1347,7 +1354,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<int> candidateCoefficients = workspace.GetCandidateCoefficients(0);
Span<TSample> prediction = workspace.Prediction;
Span<short> residual = workspace.Residual;
long bestCost = long.MaxValue;
Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1PredictionMode bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0;
selectedFilterIntraMode = Av1FilterIntraMode.AllFilterIntraModes;
@ -1439,7 +1446,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
Av1EncoderTransformBlockState candidateState = default;
long candidateCost = this.GetLumaCandidateCost(
Av1RateDistortionStatistics candidateStatistics = this.GetLumaCandidateCost(
writer,
macroBlock,
sourcePlane,
@ -1458,7 +1465,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients,
ref candidateState);
if (candidateCost < bestCost)
if (candidateStatistics.Cost < bestStatistics.Cost)
{
// The shared candidate spans are overwritten by the next transform. Copy only a
// global improvement into final block storage so no per-mode retained buffer is needed.
@ -1472,7 +1479,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateState,
ref retainedStates[0]);
bestCost = candidateCost;
bestStatistics = candidateStatistics;
bestMode = mode;
selectedAngleDelta = angleDelta;
selectedTransformSize = transformSize;
@ -1484,7 +1491,7 @@ internal static partial class Av1IntraSuperblockEncoder
if (searchEveryTransformSize &&
this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select)
{
long splitCost = this.GetSplitLumaCandidateCost(
Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer,
macroBlock,
sourcePlane,
@ -1499,12 +1506,12 @@ internal static partial class Av1IntraSuperblockEncoder
0,
paletteDisabledCost,
transformSizeContext,
bestCost,
bestStatistics.Cost,
candidateReconstruction,
candidateCoefficients,
workspace.CandidateTransformBlocks);
if (splitCost < bestCost)
if (splitStatistics.Cost < bestStatistics.Cost)
{
CopySplitCandidate(
candidateReconstruction,
@ -1515,7 +1522,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients,
retainedStates);
bestCost = splitCost;
bestStatistics = splitStatistics;
bestMode = mode;
selectedAngleDelta = angleDelta;
selectedTransformSize = Av1TransformSize.Size4x4;
@ -1525,7 +1532,7 @@ internal static partial class Av1IntraSuperblockEncoder
// Midrange effort refines the preliminary mode only. Higher effort already searched every
// mode-transform pair above, so repeating the winning mode would add no candidates.
long bestTransformCost = bestCost;
Av1RateDistortionStatistics bestTransformStatistics = bestStatistics;
if (!codedLossless && this.effort >= 3 && !searchEveryTransformType)
{
// The shared spans now contain the last mode visited above, so rebuild the preliminary
@ -1557,7 +1564,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
Av1EncoderTransformBlockState candidateState = default;
long candidateCost = this.GetLumaCandidateCost(
Av1RateDistortionStatistics candidateStatistics = this.GetLumaCandidateCost(
writer,
macroBlock,
sourcePlane,
@ -1576,7 +1583,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients,
ref candidateState);
if (candidateCost < bestTransformCost)
if (candidateStatistics.Cost < bestTransformStatistics.Cost)
{
CopyCandidate(
candidateReconstruction,
@ -1588,7 +1595,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateState,
ref retainedStates[0]);
bestTransformCost = candidateCost;
bestTransformStatistics = candidateStatistics;
}
}
}
@ -1630,7 +1637,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
Av1EncoderTransformBlockState candidateState = default;
long candidateCost = this.GetFilterIntraCandidateCost(
Av1RateDistortionStatistics candidateStatistics = this.GetFilterIntraCandidateCost(
writer,
macroBlock,
sourcePlane,
@ -1648,7 +1655,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients,
ref candidateState);
if (candidateCost < bestTransformCost)
if (candidateStatistics.Cost < bestTransformStatistics.Cost)
{
CopyCandidate(
candidateReconstruction,
@ -1660,7 +1667,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateState,
ref retainedStates[0]);
bestTransformCost = candidateCost;
bestTransformStatistics = candidateStatistics;
bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0;
selectedFilterIntraMode = filterIntraMode;
@ -1673,7 +1680,7 @@ internal static partial class Av1IntraSuperblockEncoder
if (searchEveryTransformSize &&
this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select)
{
long splitCost = this.GetSplitLumaCandidateCost(
Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer,
macroBlock,
sourcePlane,
@ -1688,12 +1695,12 @@ internal static partial class Av1IntraSuperblockEncoder
0,
paletteDisabledCost,
transformSizeContext,
bestTransformCost,
bestTransformStatistics.Cost,
candidateReconstruction,
candidateCoefficients,
workspace.CandidateTransformBlocks);
if (splitCost < bestTransformCost)
if (splitStatistics.Cost < bestTransformStatistics.Cost)
{
CopySplitCandidate(
candidateReconstruction,
@ -1704,7 +1711,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients,
retainedStates);
bestTransformCost = splitCost;
bestTransformStatistics = splitStatistics;
bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0;
selectedFilterIntraMode = filterIntraMode;
@ -1732,7 +1739,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients,
retainedCoefficients,
retainedStates,
ref bestTransformCost,
ref bestTransformStatistics,
ref paletteInfo,
ref selectedTransformSize))
{
@ -1749,7 +1756,7 @@ internal static partial class Av1IntraSuperblockEncoder
this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select &&
paletteInfo.PaletteSizes[0] == 0)
{
long splitCost = this.GetSplitLumaCandidateCost(
Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer,
macroBlock,
sourcePlane,
@ -1764,12 +1771,12 @@ internal static partial class Av1IntraSuperblockEncoder
0,
paletteDisabledCost,
transformSizeContext,
bestTransformCost,
bestTransformStatistics.Cost,
candidateReconstruction,
candidateCoefficients,
workspace.CandidateTransformBlocks);
if (splitCost < bestTransformCost)
if (splitStatistics.Cost < bestTransformStatistics.Cost)
{
CopySplitCandidate(
candidateReconstruction,
@ -1780,12 +1787,12 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients,
retainedStates);
bestTransformCost = splitCost;
bestTransformStatistics = splitStatistics;
selectedTransformSize = Av1TransformSize.Size4x4;
}
}
selectedCost = bestTransformCost;
selectedStatistics = bestTransformStatistics;
return bestMode;
}
@ -1801,7 +1808,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta,
out Av1FilterIntraMode selectedFilterIntraMode,
out Av1TransformSize selectedTransformSize,
out long selectedCost)
out Av1RateDistortionStatistics selectedStatistics)
{
Av1EncoderModeDecisionWorkspace<TSample> workspace =
this.blockWorkspace.GetModeDecisionWorkspace<TSample>();
@ -1868,7 +1875,7 @@ internal static partial class Av1IntraSuperblockEncoder
Buffer2DRegion<TSample> sourcePlane = this.source.GetPlane(Av1Plane.Y);
Buffer2DRegion<TSample> reconstructionPlane = this.reconstruction.GetPlane(Av1Plane.Y);
long bestCost = long.MaxValue;
Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1PredictionMode bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0;
selectedFilterIntraMode = Av1FilterIntraMode.AllFilterIntraModes;
@ -1942,8 +1949,8 @@ internal static partial class Av1IntraSuperblockEncoder
}
}
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
if (candidateCost < bestCost)
Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
if (candidateStatistics.Cost < bestStatistics.Cost)
{
CopyTiledCandidate(
candidateReconstruction,
@ -1957,17 +1964,17 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients,
retainedStates);
bestCost = candidateCost;
bestStatistics = candidateStatistics;
bestMode = mode;
selectedAngleDelta = angleDelta;
}
}
selectedCost = bestCost;
selectedStatistics = bestStatistics;
return bestMode;
}
private long GetSplitLumaCandidateCost(
private Av1RateDistortionStatistics GetSplitLumaCandidateCost(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Buffer2DRegion<TSample> sourcePlane,
@ -2267,12 +2274,12 @@ internal static partial class Av1IntraSuperblockEncoder
// Every remaining transform can only add nonnegative rate and distortion.
if (Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion) >= costLimit)
{
return long.MaxValue;
return Av1RateDistortionStatistics.Invalid;
}
}
}
return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
return new(this.rateMultiplier, rate, distortion);
}
private void PrepareTransformReferenceSamples(
@ -2471,7 +2478,7 @@ internal static partial class Av1IntraSuperblockEncoder
leftStorage[0] = corner;
}
private long GetLumaCandidateCost(
private Av1RateDistortionStatistics GetLumaCandidateCost(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Buffer2DRegion<TSample> sourcePlane,
@ -2546,10 +2553,10 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false);
return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
return new(this.rateMultiplier, rate, distortion);
}
private long GetFilterIntraCandidateCost(
private Av1RateDistortionStatistics GetFilterIntraCandidateCost(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Buffer2DRegion<TSample> sourcePlane,
@ -2608,7 +2615,7 @@ internal static partial class Av1IntraSuperblockEncoder
filterIntraMode,
usesInterTransformSet: false);
return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
return new(this.rateMultiplier, rate, distortion);
}
private static void CopyCandidate(

24
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.PaletteModeDecision.cs

@ -34,7 +34,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<int> candidateCoefficients,
Span<int> retainedCoefficients,
Span<Av1EncoderTransformBlockState> retainedStates,
ref long bestCost,
ref Av1RateDistortionStatistics bestStatistics,
ref Av1EncoderPaletteInfo paletteInfo,
ref Av1TransformSize selectedTransformSize)
{
@ -164,7 +164,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedStates,
retainedColorIndexMap,
reconstructionPlane,
ref bestCost,
ref bestStatistics,
ref paletteInfo,
ref selectedTransformSize,
ref paletteSelected);
@ -197,7 +197,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedStates,
retainedColorIndexMap,
reconstructionPlane,
ref bestCost,
ref bestStatistics,
ref paletteInfo,
ref selectedTransformSize,
ref paletteSelected);
@ -239,7 +239,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedStates,
retainedColorIndexMap,
reconstructionPlane,
ref bestCost,
ref bestStatistics,
ref paletteInfo,
ref selectedTransformSize,
ref paletteSelected);
@ -281,7 +281,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<Av1EncoderTransformBlockState> retainedStates,
Span<byte> retainedColorIndexMap,
Buffer2DRegion<TSample> reconstructionPlane,
ref long bestCost,
ref Av1RateDistortionStatistics bestStatistics,
ref Av1EncoderPaletteInfo paletteInfo,
ref Av1TransformSize selectedTransformSize,
ref bool paletteSelected)
@ -429,8 +429,8 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false);
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, candidateRate, distortion);
if (candidateCost < bestCost)
Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, candidateRate, distortion);
if (candidateStatistics.Cost < bestStatistics.Cost)
{
// Later palette sizes reuse every candidate span and the shared color map. Publish the
// complete palette state only when this candidate improves the global luma decision.
@ -453,7 +453,7 @@ internal static partial class Av1IntraSuperblockEncoder
paletteInfo.PaletteSizes[0] = (byte)paletteSize;
paletteInfo.SetColors(Av1Plane.Y, paletteColors);
selectedTransformSize = TransformSize;
bestCost = candidateCost;
bestStatistics = candidateStatistics;
paletteSelected = true;
}
}
@ -463,7 +463,7 @@ internal static partial class Av1IntraSuperblockEncoder
{
// Transform size is part of each palette candidate's RD result. Searching it here preserves
// candidates whose 4x4 residual partition wins even when their 8x8 result does not.
long splitCost = this.GetSplitLumaCandidateCost(
Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer,
macroBlock,
this.source.GetPlane(Av1Plane.Y),
@ -478,12 +478,12 @@ internal static partial class Av1IntraSuperblockEncoder
rate,
0,
transformSizeContext,
bestCost,
bestStatistics.Cost,
candidateReconstruction,
candidateCoefficients,
modeDecisionWorkspace.CandidateTransformBlocks);
if (splitCost < bestCost)
if (splitStatistics.Cost < bestStatistics.Cost)
{
CopySplitCandidate(
candidateReconstruction,
@ -503,7 +503,7 @@ internal static partial class Av1IntraSuperblockEncoder
paletteInfo.PaletteSizes[0] = (byte)paletteSize;
paletteInfo.SetColors(Av1Plane.Y, paletteColors);
selectedTransformSize = Av1TransformSize.Size4x4;
bestCost = splitCost;
bestStatistics = splitStatistics;
paletteSelected = true;
}
}

64
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs

@ -57,12 +57,12 @@ internal static partial class Av1IntraSuperblockEncoder
where TSample : unmanaged
where TOperator : struct, IBlockEncodingOperator<TSample>
{
private long SelectIntraBlockCopy(
private Av1RateDistortionStatistics SelectIntraBlockCopy(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
ushort tileIndex,
long regularCost,
Av1RateDistortionStatistics regularStatistics,
ref Av1MacroBlockModeInfo modeInfo,
ref Av1EncoderBlockStruct block,
ref Av1EncoderPaletteInfo paletteInfo)
@ -135,11 +135,11 @@ internal static partial class Av1IntraSuperblockEncoder
if (uniqueCandidateCount == 0)
{
return regularCost;
return regularStatistics;
}
int skipContext = Av1TileWriter.GetSkipContext(macroBlock);
long bestCost = regularCost;
Av1RateDistortionStatistics bestStatistics = regularStatistics;
bool hasSelectedCandidate = false;
bool selectedSkip = false;
Av1MotionVector selectedVector = default;
@ -336,7 +336,7 @@ internal static partial class Av1IntraSuperblockEncoder
redRate;
long candidateDistortion = lumaDistortion + blueDistortion + redDistortion;
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, candidateRate, candidateDistortion);
Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, candidateRate, candidateDistortion);
bool candidateSkip = false;
// The skip alternative is available only when every coded plane has an empty transform. Its
@ -348,21 +348,21 @@ internal static partial class Av1IntraSuperblockEncoder
writer.GetSkipCost(true, skipContext);
long skipDistortion = emptyLumaDistortion + emptyBlueDistortion + emptyRedDistortion;
long skipCost = Av1RateDistortion.GetCost(this.rateMultiplier, skipRate, skipDistortion);
if (skipCost < candidateCost)
Av1RateDistortionStatistics skipStatistics = new(this.rateMultiplier, skipRate, skipDistortion);
if (skipStatistics.Cost < candidateStatistics.Cost)
{
candidateCost = skipCost;
candidateStatistics = skipStatistics;
candidateSkip = true;
}
}
// Conventional intra and earlier IBC vectors retain strict search-order precedence on equal RD.
if (candidateCost >= bestCost)
if (candidateStatistics.Cost >= bestStatistics.Cost)
{
continue;
}
bestCost = candidateCost;
bestStatistics = candidateStatistics;
hasSelectedCandidate = true;
selectedSkip = candidateSkip;
selectedVector = candidate;
@ -410,7 +410,7 @@ internal static partial class Av1IntraSuperblockEncoder
if (!hasSelectedCandidate)
{
return bestCost;
return bestStatistics;
}
// Only the winning vector is now visible to later coding blocks. This single publication keeps
@ -481,18 +481,18 @@ internal static partial class Av1IntraSuperblockEncoder
block.PredictionUnit.ChromaFromLumaSigns = 0;
paletteInfo = default;
this.picture.SetDisplacementVector(modeInfoPosition, selectedVector);
return bestCost;
return bestStatistics;
}
/// <summary>
/// Compares the retained intra result with an inter candidate without disturbing the intra result on loss.
/// </summary>
private long SelectInterPrediction(
private Av1RateDistortionStatistics SelectInterPrediction(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
ushort tileIndex,
long regularCost,
Av1RateDistortionStatistics regularStatistics,
ref Av1MacroBlockModeInfo modeInfo,
ref Av1EncoderBlockStruct block,
ref Av1EncoderPaletteInfo paletteInfo)
@ -500,35 +500,35 @@ internal static partial class Av1IntraSuperblockEncoder
Av1MacroBlockModeInfo interModeInfo = modeInfo;
Av1EncoderBlockStruct interBlock = block;
Av1EncoderPaletteInfo interPaletteInfo = default;
long selectedCost = this.SelectInterBlock(
Av1RateDistortionStatistics selectedStatistics = this.SelectInterBlock(
writer,
macroBlock,
blockOrigin,
tileIndex,
regularCost,
regularStatistics,
ref interModeInfo,
ref interBlock,
ref interPaletteInfo);
if (selectedCost < regularCost)
if (selectedStatistics.Cost < regularStatistics.Cost)
{
modeInfo = interModeInfo;
block = interBlock;
paletteInfo = interPaletteInfo;
}
return selectedCost;
return selectedStatistics;
}
/// <summary>
/// Evaluates the supported LAST_FRAME modes and publishes only a strict improvement over the intra result.
/// </summary>
private long SelectInterBlock(
private Av1RateDistortionStatistics SelectInterBlock(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
ushort tileIndex,
long regularCost,
Av1RateDistortionStatistics regularStatistics,
ref Av1MacroBlockModeInfo modeInfo,
ref Av1EncoderBlockStruct block,
ref Av1EncoderPaletteInfo paletteInfo)
@ -651,7 +651,7 @@ internal static partial class Av1IntraSuperblockEncoder
transformPartitionRate = writer.GetTransformPartitionCost(false, transformPartitionContext);
}
long selectedCost = regularCost;
Av1RateDistortionStatistics selectedStatistics = regularStatistics;
Av1MotionVector selectedVector = default;
Av1PredictionMode selectedMode = default;
int selectedReferenceIndex = 0;
@ -785,7 +785,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
}
long candidateCost = this.EvaluateInterCandidate(
Av1RateDistortionStatistics candidateStatistics = this.EvaluateInterCandidate(
writer,
blockOrigin,
tileIndex,
@ -812,7 +812,7 @@ internal static partial class Av1IntraSuperblockEncoder
out Av1EncoderTransformBlockState candidateRedState);
// Strict replacement preserves predictor-stack, global, then new-motion order on equal RD cost.
if (candidateCost >= selectedCost)
if (candidateStatistics.Cost >= selectedStatistics.Cost)
{
continue;
}
@ -841,7 +841,7 @@ internal static partial class Av1IntraSuperblockEncoder
selectedRedCoefficients = candidateRedCoefficients;
candidateRedCoefficients = previousRedCoefficients;
selectedCost = candidateCost;
selectedStatistics = candidateStatistics;
selectedVector = candidateVectors[candidateIndex];
selectedMode = candidateModes[candidateIndex];
selectedHorizontalFilter = horizontalFilter;
@ -858,7 +858,7 @@ internal static partial class Av1IntraSuperblockEncoder
// when no inter candidate strictly improves its rate-distortion cost.
if (!hasInterWinner)
{
return regularCost;
return regularStatistics;
}
Span<int> retainedLumaCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.Y);
@ -930,7 +930,7 @@ internal static partial class Av1IntraSuperblockEncoder
modeInfo.Block.HorizontalInterpolationFilter = selectedHorizontalFilter;
block.ReferenceMotionVectorIndex = selectedReferenceIndex;
this.picture.SetDisplacementVector(modeInfoPosition, selectedVector);
return selectedCost;
return selectedStatistics;
}
/// <summary>
@ -1034,7 +1034,7 @@ internal static partial class Av1IntraSuperblockEncoder
/// <summary>
/// Evaluates one inter mode through prediction, transform, coefficient, skip, and distortion selection.
/// </summary>
private long EvaluateInterCandidate(
private Av1RateDistortionStatistics EvaluateInterCandidate(
Av1SymbolEncoder writer,
Point blockOrigin,
ushort tileIndex,
@ -1238,16 +1238,16 @@ internal static partial class Av1IntraSuperblockEncoder
redRate;
long codedDistortion = lumaDistortion + blueDistortion + redDistortion;
long selectedCost = Av1RateDistortion.GetCost(this.rateMultiplier, codedRate, codedDistortion);
Av1RateDistortionStatistics selectedStatistics = new(this.rateMultiplier, codedRate, codedDistortion);
skip = false;
if (hasEmptyLuma && hasEmptyBlue && hasEmptyRed)
{
int skipRate = predictionRate + writer.GetSkipCost(true, skipContext);
long skipDistortion = emptyLumaDistortion + emptyBlueDistortion + emptyRedDistortion;
long skipCost = Av1RateDistortion.GetCost(this.rateMultiplier, skipRate, skipDistortion);
if (skipCost < selectedCost)
Av1RateDistortionStatistics skipStatistics = new(this.rateMultiplier, skipRate, skipDistortion);
if (skipStatistics.Cost < selectedStatistics.Cost)
{
selectedCost = skipCost;
selectedStatistics = skipStatistics;
skip = true;
workspace.LumaPrediction[..LumaTransformSize.GetSize2d()].CopyTo(lumaReconstruction);
lumaCoefficients[..LumaTransformSize.GetSize2d()].Clear();
@ -1265,7 +1265,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
}
return selectedCost;
return selectedStatistics;
}
/// <summary>

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

@ -817,6 +817,29 @@ public class Av1EntropyTests
Assert.Equal(before, after);
}
[Theory]
[InlineData(128, 2, 0L, 2, 0L, 1L)]
[InlineData(128, 1, 0L, 1, 0L, 1L)]
[InlineData(64, 512, 2_000_000_000L, 512, 2_000_000_000L, 512_000_000_128L)]
public void RateDistortionStatisticsRoundCombinedRate(
int multiplier,
int firstRate,
long firstDistortion,
int secondRate,
long secondDistortion,
long expectedCost)
{
// The first two cases cross opposite sides of the half-unit boundary: independently rounded
// child costs would be two and zero, while the combined reference cost is one in both cases.
Av1RateDistortionStatistics combined = new(multiplier, firstRate, firstDistortion);
Av1RateDistortionStatistics second = new(multiplier, secondRate, secondDistortion);
combined.Add(multiplier, in second);
Assert.Equal(firstRate + secondRate, combined.Rate);
Assert.Equal(firstDistortion + secondDistortion, combined.Distortion);
Assert.Equal(expectedCost, combined.Cost);
}
[Theory]
[InlineData(1, 255, 0L, 0L)]
[InlineData(1, 256, 0L, 1L)]

165
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs

@ -888,6 +888,171 @@ public class Av1IntraSuperblockEncoderTests
Assert.InRange(reconstructedSample, (ushort)(byte.MaxValue + 1), (ushort)4095);
}
[Theory]
[InlineData(true, false)]
[InlineData(false, false)]
[InlineData(true, true)]
[InlineData(false, true)]
public void BlockDecisionRetainsUnroundedRateAndDistortion(bool isMonochrome, bool textured)
{
const int Width = 8;
const int Height = 8;
const int QIndex = 37;
Av1ColorFormat colorFormat = isMonochrome ? Av1ColorFormat.Yuv400 : Av1ColorFormat.Yuv420;
ObuColorConfig colorConfig = new()
{
IsMonochrome = isMonochrome,
SubSamplingX = true,
SubSamplingY = true,
BitDepth = Av1BitDepth.EightBit
};
using Av1EncoderFrameBuffer<byte> source = new(Configuration.Default, Width, Height, 8, colorFormat, 1, 1);
using Av1EncoderFrameBuffer<byte> reconstruction = new(Configuration.Default, Width, Height, 8, colorFormat, 1, 1);
int planeCount = isMonochrome ? 1 : 3;
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++)
{
Buffer2DRegion<byte> plane = source.Frame.CodedView.GetPlane((Av1Plane)planeIndex);
int length = planeIndex == 0 ? 8 : 4;
for (int y = 0; y < length; y++)
{
Span<byte> row = plane.DangerousGetRowSpan(y);
for (int x = 0; x < length; x++)
{
row[x] = textured ? (byte)(114 + (((x * 13) + (y * 7) + (planeIndex * 5)) % 29)) : (byte)128;
}
}
}
using Av1EncoderModeInfoBuffer modeInfoBuffer = new(Configuration.Default, Width, Height, disallow4x4AllFrames: true);
Av1PictureControlSet template = CreatePicture(modeInfoBuffer, colorConfig, use128x128Superblock: false, QIndex);
template.Parent.FrameHeader.TransformMode = Av1TransformMode.Largest;
using Av1EncoderPictureBuffer pictureBuffer = new(
Configuration.Default,
template.Sequence.SequenceHeader,
template.Parent.FrameHeader,
Width,
Height,
disallow4x4AllFrames: true);
Av1PictureControlSet picture = pictureBuffer.Picture;
using Av1EncoderCoefficientBuffer coefficients = new(Configuration.Default, template.Sequence.SequenceHeader, Width, Height);
using Av1EncoderSuperblockWorkspace superblockWorkspace = new(Configuration.Default);
using Av1EncoderBlockWorkspace blockWorkspace = new(Configuration.Default);
Av1Superblock superblock = new()
{
Workspace = superblockWorkspace,
TileInfo = new Av1TileInfo(0, 0, picture.Parent.FrameHeader),
Index = 0
};
Av1IntraSuperblockEncoder.Prepare(picture, superblock, Point.Empty);
Av1MacroBlockD macroBlock = new() { Tile = superblock.TileInfo };
Av1TileWriter.SetModeInfoRowAndColumn(
picture,
macroBlock,
superblock.TileInfo,
Point.Empty,
Av1BlockSize.Block8x8,
picture.Parent.Common.ModeInfoStride,
picture.Parent.Common.ModeInfoRowCount,
picture.Parent.Common.ModeInfoColumnCount);
Av1IntraSuperblockEncoder.ModeDecision<byte, Av1IntraSuperblockEncoder.ByteOperator> decision = new(
source.Frame,
reconstruction.Frame,
reconstruction.Frame,
picture,
superblock,
coefficients,
blockWorkspace,
effort: 0);
ref Av1MacroBlockModeInfo modeInfo = ref picture.GetMacroBlockModeInfo(Point.Empty);
Av1EncoderBlockStruct block = default;
Av1EncoderPaletteInfo palette = default;
using Av1SymbolEncoder writer = new(Configuration.Default, 256, QIndex, updateCdf: true);
decision.EncodeBlock(writer, macroBlock, Point.Empty, 0, ref modeInfo, ref block, ref palette);
Assert.Equal(Av1PredictionMode.DC, modeInfo.Block.Mode);
Assert.Equal(Av1TransformSize.Size8x8, modeInfo.Block.TransformSize);
Assert.False(modeInfo.Block.Skip);
int expectedRate = writer.GetSkipCost(false, Av1TileWriter.GetSkipContext(macroBlock));
expectedRate += Av1TileWriter.GetLumaModeCost(
writer,
macroBlock,
Av1BlockSize.Block8x8,
Av1PredictionMode.DC,
0,
isIntraFrame: true);
if (!isMonochrome)
{
Assert.Equal(Av1ChromaPredictionMode.DC, modeInfo.Block.UvMode);
expectedRate += Av1TileWriter.GetChromaModeCost(
writer,
picture.Parent.FrameHeader,
colorConfig,
modeInfo,
Av1BlockSize.Block8x8,
Av1PredictionMode.DC,
Av1ChromaPredictionMode.DC,
0);
}
long squaredError = 0;
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++)
{
Av1Plane plane = (Av1Plane)planeIndex;
Av1TransformSize transformSize = planeIndex == 0 ? Av1TransformSize.Size8x8 : Av1TransformSize.Size4x4;
Av1BlockSize blockSize = planeIndex == 0 ? Av1BlockSize.Block8x8 : Av1BlockSize.Block4x4;
Av1ComponentType component = planeIndex == 0 ? Av1ComponentType.Luminance : Av1ComponentType.Chroma;
Av1NeighborArrayUnit<byte> neighbors = planeIndex switch
{
0 => picture.LuminanceDcSignLevelCoefficientNeighbors[0],
1 => picture.CbDcSignLevelCoefficientNeighbors[0],
_ => picture.CrDcSignLevelCoefficientNeighbors[0]
};
Av1EncoderTransformBlockState state = coefficients.GetTransformBlockSpan(0, plane)[0];
expectedRate += writer.GetCoefficientCost(
transformSize,
state.TransformType,
Av1PredictionMode.DC,
coefficients.GetPlaneSpan(0, plane)[..transformSize.GetSize2d()],
component,
Av1TileWriter.GetTransformBlockContexts(component, neighbors, Point.Empty, blockSize, transformSize),
state.EndOfBlock,
picture.Parent.FrameHeader.UseReducedTransformSet,
Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false);
Buffer2DRegion<byte> sourcePlane = source.Frame.CodedView.GetPlane(plane);
Buffer2DRegion<byte> reconstructedPlane = reconstruction.Frame.CodedView.GetPlane(plane);
int length = planeIndex == 0 ? 8 : 4;
for (int y = 0; y < length; y++)
{
ReadOnlySpan<byte> sourceRow = sourcePlane.DangerousGetRowSpan(y);
ReadOnlySpan<byte> reconstructedRow = reconstructedPlane.DangerousGetRowSpan(y);
for (int x = 0; x < length; x++)
{
int difference = sourceRow[x] - reconstructedRow[x];
squaredError += difference * difference;
}
}
}
// Pixel-domain SSE uses the reference's four fractional distortion bits. The probability rate
// is rounded after all planes and block syntax have been counted, before adding scaled distortion.
long expectedDistortion = squaredError * 16;
int multiplier = Av1RateDistortion.GetKeyFrameRateMultiplier(QIndex, Av1BitDepth.EightBit);
long expectedCost = ((((long)expectedRate * multiplier) + 256) / 512) + (expectedDistortion * 128);
Assert.Equal(textured, squaredError > 0);
Assert.Equal(expectedRate, decision.SelectedBlockStatistics.Rate);
Assert.Equal(expectedDistortion, decision.SelectedBlockStatistics.Distortion);
Assert.Equal(expectedCost, decision.SelectedBlockStatistics.Cost);
}
[Fact]
public void BlockDecisionObservesLiveCdfInWriterOrder()
{

Loading…
Cancel
Save