Browse Source

Document and correct HEIF chroma prediction

pull/2633/head
James Jackson-South 1 week ago
parent
commit
5cedac91ba
  1. 69
      src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs
  2. 53
      src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs

69
src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs

@ -7,15 +7,40 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
/// <summary>
/// Accumulates subsampled luma samples and derives the zero-mean Q3 predictor surface used by AV1 chroma-from-luma prediction.
/// </summary>
internal class Av1ChromaFromLumaContext
{
/// <summary>
/// The fixed row stride and maximum dimension, in chroma samples, of the luma predictor buffer.
/// </summary>
private const int BufferLine = 32;
/// <summary>
/// The number of initialized predictor rows currently stored in <see cref="Q3Buffer"/>.
/// </summary>
private int bufferHeight;
/// <summary>
/// The number of initialized predictor columns currently stored in <see cref="Q3Buffer"/>.
/// </summary>
private int bufferWidth;
/// <summary>
/// Whether luma is subsampled by two along the horizontal axis for the chroma planes.
/// </summary>
private readonly bool subX;
/// <summary>
/// Whether luma is subsampled by two along the vertical axis for the chroma planes.
/// </summary>
private readonly bool subY;
/// <summary>
/// Initializes a new instance of the <see cref="Av1ChromaFromLumaContext"/> class.
/// </summary>
/// <param name="colorConfig">The AV1 color configuration that supplies chroma subsampling.</param>
public Av1ChromaFromLumaContext(ObuColorConfig colorConfig)
{
this.subX = colorConfig.SubSamplingX;
@ -23,10 +48,28 @@ internal class Av1ChromaFromLumaContext
this.Q3Buffer = new short[BufferLine * BufferLine];
}
/// <summary>
/// Gets the fixed-stride luma predictor samples in signed Q3 fixed-point representation.
/// </summary>
public short[] Q3Buffer { get; }
/// <summary>
/// Gets a value indicating whether edge padding and mean subtraction have been applied to the current samples.
/// </summary>
public bool AreParametersComputed { get; private set; }
/// <summary>
/// Stores one reconstructed luma transform region in the chroma-resolution Q3 predictor buffer.
/// </summary>
/// <typeparam name="T">The integer sample type of the reconstructed luma plane.</typeparam>
/// <param name="input">The reconstructed luma samples for the transform region.</param>
/// <param name="inputStride">The distance, in samples, between consecutive input rows.</param>
/// <param name="row">The transform row relative to the chroma-from-luma block, in mode-info units.</param>
/// <param name="column">The transform column relative to the chroma-from-luma block, in mode-info units.</param>
/// <param name="transformSize">The luma transform dimensions.</param>
/// <param name="blockSize">The coded luma block size used to resolve shared sub-8x8 chroma ownership.</param>
/// <param name="modeInfoRow">The frame-relative luma row in 4x4 mode-info units.</param>
/// <param name="modeInfoColumn">The frame-relative luma column in 4x4 mode-info units.</param>
public void Store<T>(
Span<T> input,
int inputStride,
@ -60,6 +103,8 @@ internal class Av1ChromaFromLumaContext
int storeColumn = column << (Av1Constants.ModeInfoSizeLog2 - subX);
int storeWidth = width >> subX;
int storeHeight = height >> subY;
// New luma samples invalidate the previously padded, zero-mean surface.
this.AreParametersComputed = false;
if (column == 0 && row == 0)
@ -122,6 +167,10 @@ internal class Av1ChromaFromLumaContext
}
}
/// <summary>
/// Pads the populated predictor extent to the transform dimensions and subtracts its rounded mean.
/// </summary>
/// <param name="transformSize">The chroma prediction transform dimensions.</param>
public void ComputeParameters(Av1TransformSize transformSize)
{
Guard.IsFalse(this.AreParametersComputed, nameof(this.AreParametersComputed), "Do not call cfl_compute_parameters multiple time on the same values.");
@ -130,6 +179,11 @@ internal class Av1ChromaFromLumaContext
this.AreParametersComputed = true;
}
/// <summary>
/// Extends the last initialized column and row to cover the requested predictor dimensions.
/// </summary>
/// <param name="width">The required predictor width in chroma samples.</param>
/// <param name="height">The required predictor height in chroma samples.</param>
private void Pad(int width, int height)
{
int differenceWidth = width - this.bufferWidth;
@ -138,6 +192,8 @@ internal class Av1ChromaFromLumaContext
if (differenceWidth > 0)
{
int minimumHeight = height - differenceHeight;
// AV1 CfL edge extension repeats the final available sample when the coded luma extent is narrower.
for (int y = 0; y < minimumHeight; y++)
{
int rowOffset = y * BufferLine;
@ -150,6 +206,7 @@ internal class Av1ChromaFromLumaContext
if (differenceHeight > 0)
{
// Missing bottom rows repeat the last available row after horizontal extension is complete.
for (int y = this.bufferHeight; y < height; y++)
{
int rowOffset = y * BufferLine;
@ -160,14 +217,18 @@ internal class Av1ChromaFromLumaContext
}
}
/************************************************************************************************
* svt_subtract_average_c
* Calculate the DC value by averaging over all sample. Subtract DC value to get AC values In C
************************************************************************************************/
/// <summary>
/// Subtracts the rounded Q3 average from each predictor sample, leaving the AC contribution used by CfL.
/// </summary>
/// <param name="transformSize">The populated predictor dimensions.</param>
/// <remarks>SVT-AV1: <c>svt_subtract_average_c</c>.</remarks>
private void SubtractAverage(Av1TransformSize transformSize)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
// Transform dimensions are powers of two, so division by the sample count is an exact right shift.
// Half the sample count is accumulated first to round the signed Q3 mean to the nearest integer.
int roundOffset = (width * height) >> 1;
int pelCountLog2 = transformSize.GetBlockWidthLog2() + transformSize.GetBlockHeightLog2();
int sumQ3 = roundOffset;

53
src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs

@ -3,24 +3,75 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
/// <summary>
/// Provides AV1 chroma-from-luma sign, magnitude-index, and entropy-context mappings.
/// </summary>
internal static class Av1ChromaFromLumaMath
{
/// <summary>
/// The number of alpha sign states: zero, negative, and positive.
/// </summary>
private const int Signs = 3;
/// <summary>
/// The number of bits occupied by each plane's packed alpha-magnitude index.
/// </summary>
private const int AlphabetSizeLog2 = 4;
/// <summary>
/// The alpha sign value representing a zero multiplier.
/// </summary>
public const int SignZero = 0;
/// <summary>
/// The alpha sign value representing a negative multiplier.
/// </summary>
public const int SignNegative = 1;
/// <summary>
/// The alpha sign value representing a positive multiplier.
/// </summary>
public const int SignPositive = 2;
/// <summary>
/// Extracts the U-plane sign from a joint chroma sign symbol.
/// </summary>
/// <param name="jointSign">The coded joint U/V sign symbol.</param>
/// <returns>The U-plane sign state.</returns>
public static int SignU(int jointSign) => ((jointSign + 1) * 11) >> 5;
/// <summary>
/// Extracts the V-plane sign from a joint chroma sign symbol.
/// </summary>
/// <param name="jointSign">The coded joint U/V sign symbol.</param>
/// <returns>The V-plane sign state.</returns>
public static int SignV(int jointSign) => (jointSign + 1) - (Signs * SignU(jointSign));
/// <summary>
/// Extracts the U-plane alpha-magnitude index from the high four bits of the packed index.
/// </summary>
/// <param name="index">The packed U/V alpha-magnitude index.</param>
/// <returns>The U-plane magnitude index.</returns>
public static int IndexU(int index) => index >> AlphabetSizeLog2;
public static int IndexV(int index) => index & (AlphabetSizeLog2 - 1);
/// <summary>
/// Extracts the V-plane alpha-magnitude index from the low four bits of the packed index.
/// </summary>
/// <param name="index">The packed U/V alpha-magnitude index.</param>
/// <returns>The V-plane magnitude index.</returns>
public static int IndexV(int index) => index & ((1 << AlphabetSizeLog2) - 1);
/// <summary>
/// Maps a joint sign symbol to the entropy context used for the U-plane alpha magnitude.
/// </summary>
/// <param name="jointSign">The coded joint U/V sign symbol.</param>
/// <returns>The U-plane alpha entropy context.</returns>
public static int ContextU(int jointSign) => jointSign + 1 - Signs;
/// <summary>
/// Maps a joint sign symbol to the symmetric entropy context used for the V-plane alpha magnitude.
/// </summary>
/// <param name="jointSign">The coded joint U/V sign symbol.</param>
/// <returns>The V-plane alpha entropy context.</returns>
public static int ContextV(int jointSign) => (SignV(jointSign) * Signs) + SignU(jointSign) - Signs;
}

Loading…
Cancel
Save