diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
index 19728b0d87..f5ee4139c2 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
@@ -11,13 +11,34 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+///
+/// Reconstructs AV1 intra-predicted transform blocks from neighboring samples and decoded mode information.
+///
+///
+/// This type implements the intra prediction portion of the AV1 reconstruction process for 8-, 10-, and 12-bit samples.
+///
internal class Av1PredictionDecoder
{
+ ///
+ /// The largest edge length for which AV1 permits intra-edge upsampling.
+ ///
private const int MaxUpsampleSize = 16;
+ ///
+ /// The sequence-level syntax that controls chroma sampling, bit depth, superblock size, and intra-edge filtering.
+ ///
private readonly ObuSequenceHeader sequenceHeader;
+
+ ///
+ /// The frame-level syntax that controls segment lossless state and prediction behavior.
+ ///
private readonly ObuFrameHeader frameHeader;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The decoded sequence header for the current image.
+ /// The decoded frame header for the current image.
public Av1PredictionDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
this.sequenceHeader = sequenceHeader;
@@ -25,8 +46,18 @@ internal class Av1PredictionDecoder
}
///
- /// SVT: svt_av1_predict_intra
+ /// Reconstructs an 8-bit intra-predicted transform block.
///
+ /// The decoded partition and mode state for the containing block.
+ /// The color plane being reconstructed.
+ /// The dimensions of the transform block.
+ /// The tile boundaries used to determine neighboring-sample availability.
+ /// The sample buffer beginning at the row above the destination block.
+ /// The distance, in samples, between pixel rows.
+ /// The bit depth of the reconstructed samples.
+ /// The transform block's horizontal offset within the mode-information block.
+ /// The transform block's vertical offset within the mode-information block.
+ /// Corresponds to svt_av1_predict_intra in SVT-AV1.
public void Decode(
Av1PartitionInfo partitionInfo,
Av1Plane plane,
@@ -49,8 +80,18 @@ internal class Av1PredictionDecoder
blockModeInfoRowOffset);
///
- /// AV1: 7.11.2 Reconstruct.
+ /// Reconstructs a 10-bit or 12-bit intra-predicted transform block.
///
+ /// The decoded partition and mode state for the containing block.
+ /// The color plane being reconstructed.
+ /// The dimensions of the transform block.
+ /// The tile boundaries used to determine neighboring-sample availability.
+ /// The sample buffer beginning at the row above the destination block.
+ /// The distance, in samples, between pixel rows.
+ /// The bit depth of the reconstructed samples.
+ /// The transform block's horizontal offset within the mode-information block.
+ /// The transform block's vertical offset within the mode-information block.
+ /// Implements the intra prediction portion of section 7.11.2 of the AV1 specification.
public void Decode(
Av1PartitionInfo partitionInfo,
Av1Plane plane,
@@ -72,6 +113,19 @@ internal class Av1PredictionDecoder
blockModeInfoColumnOffset,
blockModeInfoRowOffset);
+ ///
+ /// Reconstructs an intra-predicted transform block in its native sample representation.
+ ///
+ /// The 8-bit or high-bit-depth sample type.
+ /// The decoded partition and mode state for the containing block.
+ /// The color plane being reconstructed.
+ /// The dimensions of the transform block.
+ /// The tile boundaries used to determine neighboring-sample availability.
+ /// The sample buffer beginning at the row above the destination block.
+ /// The distance, in samples, between pixel rows.
+ /// The bit depth of the reconstructed samples.
+ /// The transform block's horizontal offset within the mode-information block.
+ /// The transform block's vertical offset within the mode-information block.
private void DecodeCore(
Av1PartitionInfo partitionInfo,
Av1Plane plane,
@@ -86,7 +140,8 @@ internal class Av1PredictionDecoder
{
int stride = pixelStride;
- // Deviation from SVT: Buffer starts at PREVIOUS row.
+ // Unlike SVT's separate destination and reference pointers, this span begins at the
+ // previous row. That layout exposes the top, top-left, and strided left samples without copying.
Span topNeighbor = pixelBuffer;
Span leftNeighbor = pixelBuffer[(stride - 1)..];
Span startOfPixels = pixelBuffer[stride..];
@@ -137,6 +192,16 @@ internal class Av1PredictionDecoder
bitDepth);
}
+ ///
+ /// Applies chroma-from-luma scaling to the DC prediction for one chroma transform block.
+ ///
+ /// The 8-bit or high-bit-depth sample type.
+ /// The decoded partition and mode state for the containing block.
+ /// The block-level luma prediction context shared by the chroma planes.
+ /// The DC-predicted chroma samples that receive the luma-derived adjustment.
+ /// The distance, in samples, between pixel rows.
+ /// The dimensions of the chroma transform block.
+ /// The U or V plane being reconstructed.
private void PredictChromaFromLumaBlock(Av1PartitionInfo partitionInfo, Av1ChromaFromLumaContext? chromaFromLumaContext, Span pixelBuffer, int stride, Av1TransformSize transformSize, Av1Plane plane)
where T : unmanaged, IBinaryInteger
{
@@ -149,14 +214,15 @@ internal class Av1PredictionDecoder
throw new InvalidOperationException("CFL context should have been defined already.");
}
+ // U computes the shared subsampled-luma parameters first; V reuses them for the
+ // same block because both chroma planes have identical sampling geometry.
if (!chromaFromLumaContext.AreParametersComputed)
{
chromaFromLumaContext.ComputeParameters(transformSize);
}
- int alphaQ3 = ChromaFromLumaIndexToAlpha(modeInfo.ChromaFromLumaAlphaIndex, modeInfo.ChromaFromLumaAlphaSign, (Av1Plane)((int)plane - 1));
+ int alphaQ3 = ChromaFromLumaIndexToAlpha(modeInfo.ChromaFromLumaAlphaIndex, modeInfo.ChromaFromLumaAlphaSign, plane);
- // assert((transformSize.GetHeight() - 1) * CFL_BUF_LINE + transformSize.GetWidth() <= CFL_BUF_SQUARE);
Av1BitDepth bitDepth = this.sequenceHeader.ColorConfig.BitDepth;
ChromaFromLumaPredict(
chromaFromLumaContext.Q3Buffer,
@@ -170,11 +236,18 @@ internal class Av1PredictionDecoder
transformSize.GetHeight());
}
+ ///
+ /// Determines whether chroma-from-luma prediction is permitted for the current block and frame state.
+ ///
+ /// The decoded partition and mode state for the containing block.
+ /// The sequence color configuration.
+ /// The decoded frame header.
+ /// when the block may use chroma-from-luma prediction; otherwise, .
private static bool IsChromaFromLumaAllowedWithFrameHeader(Av1PartitionInfo partitionInfo, ObuColorConfig colorConfig, ObuFrameHeader frameHeader)
{
Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo;
Av1BlockSize blockSize = modeInfo.BlockSize;
- DebugGuard.MustBeGreaterThan((int)blockSize, (int)Av1BlockSize.AllSizes, nameof(blockSize));
+ DebugGuard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.AllSizes, nameof(blockSize));
if (frameHeader.LosslessArray[modeInfo.SegmentId])
{
// In lossless, CfL is available when the partition size is equal to the
@@ -185,11 +258,18 @@ internal class Av1PredictionDecoder
return planeBlockSize == Av1BlockSize.Block4x4;
}
- // Spec: CfL is available to luma partitions lesser than or equal to 32x32
+ // Outside lossless mode, AV1 limits CfL to luma partitions no larger than 32 by 32.
return blockSize.GetWidth() <= 32 && blockSize.GetHeight() <= 32;
}
- private static int ChromaFromLumaIndexToAlpha(int alphaIndex, int jointSign, Av1Plane plane)
+ ///
+ /// Converts the packed chroma-from-luma magnitude and joint sign into a signed Q3 scaling factor.
+ ///
+ /// The packed U and V alpha magnitudes.
+ /// The joint U and V alpha-sign symbol.
+ /// The U or V plane whose alpha value is selected.
+ /// The signed Q3 alpha value for the selected chroma plane.
+ public static int ChromaFromLumaIndexToAlpha(int alphaIndex, int jointSign, Av1Plane plane)
{
int alphaSign = (plane == Av1Plane.U) ? Av1ChromaFromLumaMath.SignU(jointSign) : Av1ChromaFromLumaMath.SignV(jointSign);
if (alphaSign == Av1ChromaFromLumaMath.SignZero)
@@ -201,13 +281,32 @@ internal class Av1PredictionDecoder
return (alphaSign == Av1ChromaFromLumaMath.SignPositive) ? absAlphaQ3 + 1 : -absAlphaQ3 - 1;
}
+ ///
+ /// Multiplies a Q3 chroma-from-luma alpha by a Q3 luma residual and returns an integer sample adjustment.
+ ///
+ /// The signed Q3 chroma scaling factor.
+ /// The signed Q3 luma residual.
+ /// The signed integer sample adjustment.
private static int GetScaledLumaQ0(int alphaQ3, short predictedQ3)
{
int scaledLumaQ6 = alphaQ3 * predictedQ3;
return Av1Math.RoundPowerOf2Signed(scaledLumaQ6, 6);
}
- internal static void ChromaFromLumaPredict(Span predictedBufferQ3, Span predictedBuffer, int predictedStride, Span destinationBuffer, int destinationStride, int alphaQ3, Av1BitDepth bitDepth, int width, int height)
+ ///
+ /// Applies a chroma-from-luma residual to a chroma prediction block.
+ ///
+ /// The 8-bit or high-bit-depth sample type.
+ /// The Q3 luma residuals stored with the normative 32-sample CfL row stride.
+ /// The base chroma prediction samples.
+ /// The distance, in samples, between base prediction rows.
+ /// The buffer that receives the adjusted chroma samples.
+ /// The distance, in samples, between destination rows.
+ /// The signed Q3 chroma scaling factor.
+ /// The bit depth used to clamp reconstructed samples.
+ /// The width of the prediction block in samples.
+ /// The height of the prediction block in samples.
+ public static void ChromaFromLumaPredict(Span predictedBufferQ3, Span predictedBuffer, int predictedStride, Span destinationBuffer, int destinationStride, int alphaQ3, Av1BitDepth bitDepth, int width, int height)
where T : unmanaged, IBinaryInteger
{
// TODO: Make SIMD variant of this method.
@@ -223,10 +322,29 @@ internal class Av1PredictionDecoder
destinationBuffer = destinationBuffer[destinationStride..];
predictedBuffer = predictedBuffer[predictedStride..];
+
+ // The CfL scratch buffer has a fixed 32-sample row stride independent of transform width.
predictedBufferQ3 = predictedBufferQ3[32..];
}
}
+ ///
+ /// Determines available reference samples and dispatches prediction for one transform block.
+ ///
+ /// The 8-bit or high-bit-depth sample type.
+ /// The decoded partition and mode state for the containing block.
+ /// The color plane being reconstructed.
+ /// The dimensions of the transform block.
+ /// The tile boundaries used to determine neighboring-sample availability.
+ /// The destination samples for the transform block.
+ /// The distance, in samples, between destination rows.
+ /// The reconstructed samples along the top edge.
+ /// The reconstructed samples along the left edge.
+ /// The distance, in samples, between consecutive left-edge references.
+ /// The intra prediction mode to apply.
+ /// The transform block's horizontal offset within the mode-information block.
+ /// The transform block's vertical offset within the mode-information block.
+ /// The bit depth of the reconstructed samples.
private void PredictIntraBlock(
Av1PartitionInfo partitionInfo,
Av1Plane plane,
@@ -260,6 +378,8 @@ internal class Av1PredictionDecoder
if (usePalette)
{
+ // Palette blocks use their decoded color-index map rather than neighboring-sample
+ // prediction. The tile parser currently rejects palette syntax before this point.
return;
}
@@ -277,11 +397,11 @@ internal class Av1PredictionDecoder
int xrOffset = 0;
int ydOffset = 0;
- // Distance between right edge of this pred block to frame right edge
+ // These distances bound edge extension at the coded frame rather than allowing
+ // a transform to read padding that happens to exist beyond the visible image.
int xr = (partitionInfo.ModeBlockToRightEdge >> (3 + subX)) + (partitionInfo.WidthInPixels[(int)plane] - (blockModeInfoColumnOffset << Av1Constants.ModeInfoSizeLog2) - transformWidth) -
xrOffset;
- // Distance between bottom edge of this pred block to frame bottom edge
int yd = (partitionInfo.ModeBlockToBottomEdge >> (3 + subY)) +
(partitionInfo.HeightInPixels[(int)plane] - (blockModeInfoRowOffset << Av1Constants.ModeInfoSizeLog2) - transformHeight) - ydOffset;
bool rightAvailable = modeInfoColumn + ((blockModeInfoColumnOffset + transformWidthInModeInfoUnits) << subX) < tileInfo.ModeInfoColumnEnd;
@@ -289,7 +409,7 @@ internal class Av1PredictionDecoder
Av1PartitionType partition = modeInfo.PartitionType;
- // force 4x4 chroma component block size.
+ // Chroma prediction geometry cannot be smaller than 4 by 4 after subsampling.
blockSize = ScaleChromaBlockSize(blockSize, subX == 1, subY == 1);
bool haveTopRight = IntraHasTopRight(
@@ -342,6 +462,13 @@ internal class Av1PredictionDecoder
bitDepth.GetBitCount());
}
+ ///
+ /// Adjusts sub-8-by-8 luma block geometry to the minimum chroma prediction block size.
+ ///
+ /// The luma block size.
+ /// A value indicating whether chroma is horizontally subsampled.
+ /// A value indicating whether chroma is vertically subsampled.
+ /// The block size used to evaluate chroma reference availability.
private static Av1BlockSize ScaleChromaBlockSize(Av1BlockSize blockSize, bool subX, bool subY)
{
Av1BlockSize bs = blockSize;
@@ -429,6 +556,22 @@ internal class Av1PredictionDecoder
return bs;
}
+ ///
+ /// Determines whether every bottom-left reference sample required by a transform is already reconstructed.
+ ///
+ /// The sequence superblock size.
+ /// The containing block size in the current plane's geometry.
+ /// The containing block row in 4-by-4 mode-information units.
+ /// The containing block column in 4-by-4 mode-information units.
+ /// A value indicating whether the required rows remain inside the frame and tile.
+ /// A value indicating whether reconstructed samples exist immediately to the left.
+ /// The partition type that determines reconstruction order.
+ /// The transform size whose extended edge is required.
+ /// The transform row offset within the containing block.
+ /// The transform column offset within the containing block.
+ /// The horizontal chroma subsampling shift.
+ /// The vertical chroma subsampling shift.
+ /// when the bottom-left reference extension is available; otherwise, .
private static bool IntraHasBottomLeft(Av1BlockSize superblockSize, Av1BlockSize blockSize, int modeInfoRow, int modeInfoColumn, bool bottomAvailable, bool haveLeft, Av1PartitionType partition, Av1TransformSize transformSize, int blockModeInfoRowOffset, int blockModeInfoColumnOffset, int subX, int subY)
{
if (!bottomAvailable || !haveLeft)
@@ -436,9 +579,8 @@ internal class Av1PredictionDecoder
return false;
}
- // Special case for 128x* blocks, when col_off is half the block width.
- // This is needed because 128x* superblocks are divided into 64x* blocks in
- // raster order
+ // A 128-wide block is reconstructed as two 64-wide regions in raster order,
+ // so the right half can consume references that already belong to the left half.
if (blockSize.GetWidth() > 64 && blockModeInfoColumnOffset > 0)
{
int planeBlockWidthInUnits64 = 64 >> subX;
@@ -504,6 +646,22 @@ internal class Av1PredictionDecoder
}
}
+ ///
+ /// Determines whether every top-right reference sample required by a transform is already reconstructed.
+ ///
+ /// The sequence superblock size.
+ /// The containing block size in the current plane's geometry.
+ /// The containing block row in 4-by-4 mode-information units.
+ /// The containing block column in 4-by-4 mode-information units.
+ /// A value indicating whether reconstructed samples exist immediately above.
+ /// A value indicating whether the required columns remain inside the frame and tile.
+ /// The partition type that determines reconstruction order.
+ /// The transform size whose extended edge is required.
+ /// The transform row offset within the containing block.
+ /// The transform column offset within the containing block.
+ /// The horizontal chroma subsampling shift.
+ /// The vertical chroma subsampling shift.
+ /// when the top-right reference extension is available; otherwise, .
private static bool IntraHasTopRight(Av1BlockSize superblockSize, Av1BlockSize blockSize, int modeInfoRow, int modeInfoColumn, bool haveTop, bool rightAvailable, Av1PartitionType partition, Av1TransformSize transformSize, int blockModeInfoRowOffset, int blockModeInfoColumnOffset, int subX, int subY)
{
if (!haveTop || !rightAvailable)
@@ -516,7 +674,9 @@ internal class Av1PredictionDecoder
int topRightUnitCount = transformSize.Get4x4WideCount();
if (blockModeInfoRowOffset > 0)
- { // Just need to check if enough pixels on the right.
+ {
+ // Transforms below the first row obtain their top edge from the containing block,
+ // so only the reconstructed width to their right constrains availability.
if (blockSize.GetWidth() > 64)
{
// Special case: For 128x128 blocks, the transform unit whose
@@ -570,6 +730,27 @@ internal class Av1PredictionDecoder
}
}
+ ///
+ /// Prepares normative reference-edge samples and runs the selected intra predictor.
+ ///
+ /// The 8-bit or high-bit-depth sample type.
+ /// The decoded partition and neighboring mode state.
+ /// The reconstructed top and top-right reference samples.
+ /// The reconstructed left and bottom-left reference samples.
+ /// The distance, in samples, between consecutive left-edge references.
+ /// The buffer that receives the prediction block.
+ /// The distance, in samples, between destination rows.
+ /// The intra prediction mode to apply.
+ /// The coded directional angle adjustment.
+ /// The selected filter intra mode, or the sentinel indicating that filter intra is disabled.
+ /// The dimensions of the prediction block.
+ /// A value indicating whether intra-edge filtering and upsampling are disabled.
+ /// The number of available top samples.
+ /// The number of available top-right extension samples.
+ /// The number of available left samples.
+ /// The number of available bottom-left extension samples.
+ /// The color plane being reconstructed.
+ /// The number of bits used to represent each sample.
private void DecodeBuildIntraPredictors(
Av1PartitionInfo partitionInfo,
Span aboveNeighbor,
@@ -591,6 +772,9 @@ internal class Av1PredictionDecoder
where T : unmanaged, IBinaryInteger
{
int baseValue = 128 << (bitDepth - 8);
+
+ // Prefix storage is required because AV1 addresses the shared top-left sample at -1
+ // and writes upsampled edge samples as far back as -2.
Span aboveData = stackalloc T[(Av1Constants.MaxTransformSize * 2) + 32];
Span leftData = stackalloc T[(Av1Constants.MaxTransformSize * 2) + 32];
aboveData.Fill(T.CreateChecked(baseValue - 1));
@@ -644,6 +828,8 @@ internal class Av1PredictionDecoder
if ((!needAbove && leftPixelCount == 0) || (!needLeft && topPixelCount == 0))
{
+ // Pure horizontal or vertical prediction with its sole required edge missing
+ // degenerates to the first perpendicular sample or the normative midpoint offset.
T value;
if (needLeft)
{
@@ -662,7 +848,8 @@ internal class Av1PredictionDecoder
return;
}
- // NEED_LEFT
+ // Copy the available left and bottom-left samples, then extend the final sample
+ // through any unavailable portion required by the selected predictor.
if (needLeft)
{
bool needBottom = (need & Av1NeighborNeed.BottomLeft) == Av1NeighborNeed.BottomLeft;
@@ -712,7 +899,8 @@ internal class Av1PredictionDecoder
}
}
- // NEED_ABOVE
+ // Prepare the top edge by the same copy-and-extend rule. Unlike the left edge,
+ // these samples are contiguous in the reconstructed pixel buffer.
if (needAbove)
{
bool needRight = (need & Av1NeighborNeed.AboveRight) == Av1NeighborNeed.AboveRight;
@@ -758,6 +946,8 @@ internal class Av1PredictionDecoder
if (needAboveLeft)
{
+ // AV1 synthesizes the shared corner from the closest available edge when only
+ // one edge exists, and uses the bit-depth midpoint when neither edge exists.
ref T aboveLeft = ref Unsafe.Subtract(ref aboveRow[0], 1);
if (topPixelCount > 0 && leftPixelCount > 0)
{
@@ -840,7 +1030,6 @@ internal class Av1PredictionDecoder
return;
}
- // predict
if (mode == Av1PredictionMode.DC)
{
DcPredictor(leftPixelCount > 0, topPixelCount > 0, transformSize, destination, destinationStride, aboveRow, leftColumn, bitDepth);
@@ -851,9 +1040,23 @@ internal class Av1PredictionDecoder
}
}
+ ///
+ /// Dispatches DC prediction to the 8-bit or high-bit-depth implementation.
+ ///
+ /// The byte or 16-bit sample type.
+ /// A value indicating whether reconstructed left samples are available.
+ /// A value indicating whether reconstructed top samples are available.
+ /// The dimensions of the prediction block.
+ /// The buffer that receives the predicted samples.
+ /// The distance, in samples, between destination rows.
+ /// The prepared top reference samples.
+ /// The prepared left reference samples.
+ /// The number of bits used to represent each sample.
private static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span destination, nuint destinationStride, Span above, Span left, int bitDepth)
where T : unmanaged
{
+ // DecodeCore is reachable only through byte and short overloads, so this type
+ // dispatch permits shared reference preparation without boxing or allocating.
if (typeof(T) == typeof(byte))
{
Av1PredictorFactory.DcPredictor(
@@ -879,6 +1082,16 @@ internal class Av1PredictionDecoder
}
}
+ ///
+ /// Dispatches nondirectional prediction to the 8-bit or high-bit-depth implementation.
+ ///
+ /// The byte or 16-bit sample type.
+ /// The nondirectional prediction mode to apply.
+ /// The dimensions of the prediction block.
+ /// The buffer that receives the predicted samples.
+ /// The distance, in samples, between destination rows.
+ /// The prepared top reference samples.
+ /// The prepared left reference samples.
private static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, Span destination, nuint destinationStride, Span above, Span left)
where T : unmanaged
{
@@ -904,6 +1117,19 @@ internal class Av1PredictionDecoder
}
}
+ ///
+ /// Dispatches directional prediction to the 8-bit or high-bit-depth implementation.
+ ///
+ /// The byte or 16-bit sample type.
+ /// The buffer that receives the predicted samples.
+ /// The distance, in samples, between destination rows.
+ /// The dimensions of the prediction block.
+ /// The prepared top reference samples.
+ /// The prepared left reference samples.
+ /// A value indicating whether the top edge was upsampled.
+ /// A value indicating whether the left edge was upsampled.
+ /// The adjusted prediction angle in degrees.
+ /// The number of bits used to represent each sample.
private static void DirectionalPredictor(Span destination, nuint destinationStride, Av1TransformSize transformSize, Span above, Span left, bool upsampleAbove, bool upsampleLeft, int angle, int bitDepth)
where T : unmanaged
{
@@ -934,6 +1160,17 @@ internal class Av1PredictionDecoder
}
}
+ ///
+ /// Dispatches filter intra prediction to the 8-bit or high-bit-depth implementation.
+ ///
+ /// The byte or 16-bit sample type.
+ /// The buffer that receives the predicted samples.
+ /// The distance, in samples, between destination rows.
+ /// The dimensions of the prediction block.
+ /// The prepared top reference samples.
+ /// The prepared left reference samples.
+ /// The filter intra mode whose coefficient set is applied.
+ /// The number of bits used to represent each sample.
private static void FilterIntraPredictor(Span destination, nuint destinationStride, Av1TransformSize transformSize, Span above, Span left, Av1FilterIntraMode mode, int bitDepth)
where T : unmanaged
{
@@ -960,18 +1197,24 @@ internal class Av1PredictionDecoder
}
}
+ ///
+ /// Inserts half-sample positions into a prepared intra-prediction edge.
+ ///
+ /// The 8-bit or high-bit-depth sample type.
+ /// The edge buffer, including writable prefix storage at indices -2 and -1.
+ /// The number of original edge samples to upsample.
+ /// The number of bits used to clamp interpolated samples.
private static void UpsampleIntraEdge(Span buffer, int count, int bitDepth)
where T : unmanaged, IBinaryInteger
{
// TODO: Consider creating SIMD version
-
- // interpolate half-sample positions
DebugGuard.MustBeLessThanOrEqualTo(count, MaxUpsampleSize, nameof(count));
Span input = stackalloc T[MaxUpsampleSize + 3];
T beforeBuffer = Unsafe.Subtract(ref buffer[0], 1);
- // copy p[-1..(sz-1)] and extend first and last samples
+ // Duplicate both endpoints so the four-tap interpolation kernel can run at
+ // the edge without a separate boundary branch for its outer samples.
input[0] = beforeBuffer;
input[1] = beforeBuffer;
for (int i = 0; i < count; i++)
@@ -981,7 +1224,6 @@ internal class Av1PredictionDecoder
input[count + 2] = buffer[count - 1];
- // interpolate half-sample edge positions
Unsafe.Subtract(ref buffer[0], 2) = input[0];
ref T output = ref buffer[0];
for (int i = 0; i < count; i++)
@@ -996,9 +1238,15 @@ internal class Av1PredictionDecoder
}
///
- /// SVT: svt_aom_use_intra_edge_upsample
+ /// Determines whether AV1 intra-edge upsampling applies to a directional prediction edge.
///
- private static bool UseIntraEdgeUpsample(int width, int height, int delta, bool type)
+ /// The edge's primary block dimension.
+ /// The edge's secondary block dimension.
+ /// The prediction angle relative to the edge's cardinal direction.
+ /// A value indicating whether a neighboring smooth mode selects the alternate thresholds.
+ /// when the edge must be upsampled; otherwise, .
+ /// Corresponds to svt_aom_use_intra_edge_upsample in SVT-AV1.
+ private static bool UseIntraEdgeUpsample(int width, int height, int delta, bool filterType)
{
int d = Math.Abs(delta);
if (d is <= 0 or >= 40)
@@ -1007,12 +1255,17 @@ internal class Av1PredictionDecoder
}
int widthHeight = width + height;
- return type ? (widthHeight <= 8) : (widthHeight <= 16);
+ return filterType ? (widthHeight <= 8) : (widthHeight <= 16);
}
///
- /// SVT: svt_av1_filter_intra_edge_c
+ /// Applies the AV1 intra-edge smoothing kernel at the requested strength.
///
+ /// The 8-bit or high-bit-depth sample type.
+ /// A reference to the first edge sample to filter.
+ /// The number of edge samples.
+ /// The AV1 filter-strength index from zero through three.
+ /// Corresponds to svt_av1_filter_intra_edge_c in SVT-AV1.
private static void FilterIntraEdge(ref T buffer, int count, int strength)
where T : unmanaged, IBinaryInteger
{
@@ -1028,6 +1281,8 @@ internal class Av1PredictionDecoder
int filt = strength - 1;
Span edge = stackalloc T[129];
+ // Filtering must read the original edge throughout the convolution, so retain
+ // a scratch copy rather than feeding earlier filtered samples into later outputs.
MemoryMarshal.CreateSpan(ref buffer, count).CopyTo(edge);
for (int i = 1; i < count; i++)
{
@@ -1046,8 +1301,14 @@ internal class Av1PredictionDecoder
}
///
- /// SVT: svt_aom_intra_edge_filter_strength
+ /// Selects the AV1 intra-edge filter strength for the block dimensions and prediction angle.
///
+ /// The edge's primary block dimension.
+ /// The edge's secondary block dimension.
+ /// The prediction angle relative to the edge's cardinal direction.
+ /// A value indicating whether a neighboring smooth mode selects the alternate thresholds.
+ /// The filter strength from zero for no filtering through three for the strongest kernel.
+ /// Corresponds to svt_aom_intra_edge_filter_strength in SVT-AV1.
private static int IntraEdgeFilterStrength(int width, int height, int delta, bool filterType)
{
int d = Math.Abs(delta);
@@ -1163,6 +1424,12 @@ internal class Av1PredictionDecoder
return strength;
}
+ ///
+ /// Smooths the shared top-left reference sample where the prepared top and left edges meet.
+ ///
+ /// The 8-bit or high-bit-depth sample type.
+ /// The prepared top edge with writable top-left prefix storage.
+ /// The prepared left edge with writable top-left prefix storage.
private static void FilterIntraEdgeCorner(Span above, Span left)
where T : unmanaged, IBinaryInteger
{
@@ -1174,10 +1441,19 @@ internal class Av1PredictionDecoder
ref T leftPreviousRef = ref Unsafe.Subtract(ref leftRef, 1);
int s = (int.CreateChecked(leftRef) * kernel[0]) + (int.CreateChecked(abovePreviousRef) * kernel[1]) + (int.CreateChecked(aboveRef) * kernel[2]);
s = (s + 8) >> 4;
+
+ // Both edge spans reserve their own prefix location for the same logical corner,
+ // so keep the two scratch representations synchronized after filtering.
abovePreviousRef = T.CreateChecked(s);
leftPreviousRef = T.CreateChecked(s);
}
+ ///
+ /// Determines the directional edge-filter threshold class from neighboring prediction modes.
+ ///
+ /// The decoded partition and neighboring mode state.
+ /// The color plane whose neighbors are inspected.
+ /// when either relevant neighbor uses a smooth mode; otherwise, .
private static bool GetFilterType(Av1PartitionInfo partitionInfo, Av1Plane plane)
{
Av1BlockModeInfo? above;
@@ -1198,6 +1474,12 @@ internal class Av1PredictionDecoder
return aboveIsSmooth || leftIsSmooth;
}
+ ///
+ /// Determines whether a block uses any AV1 smooth intra prediction mode on the requested plane.
+ ///
+ /// The neighboring block's decoded mode state.
+ /// The luma or chroma plane class whose mode is inspected.
+ /// for smooth, smooth-horizontal, or smooth-vertical prediction; otherwise, .
private static bool IsSmooth(Av1BlockModeInfo modeInfo, Av1Plane plane)
{
if (plane == Av1Plane.Y)
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ChromaFromLumaTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ChromaFromLumaTests.cs
index ca172fb21b..e483bcef0e 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ChromaFromLumaTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ChromaFromLumaTests.cs
@@ -121,6 +121,23 @@ public class Av1ChromaFromLumaTests
Assert.Equal(new short[] { 0, (short)((maximum / 2) - 16), maximum, maximum }, destination);
}
+ [Theory]
+ [InlineData((int)Av1Plane.U, 3)]
+ [InlineData((int)Av1Plane.V, 4)]
+ public void AlphaIndexSelectsMagnitudeForRequestedChromaPlane(int planeIndex, int expected)
+ {
+ // U occupies the high nibble and V occupies the low nibble in the packed AV1 alpha index.
+ const int alphaIndex = 0x23;
+ const int bothPositiveJointSign = 7;
+
+ int actual = Av1PredictionDecoder.ChromaFromLumaIndexToAlpha(
+ alphaIndex,
+ bothPositiveJointSign,
+ (Av1Plane)planeIndex);
+
+ Assert.Equal(expected, actual);
+ }
+
private static short[] GetBlock(short[] buffer, int width, int height)
{
short[] result = new short[width * height];