diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
index 8b2e01b6e..e9f8b86b4 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
@@ -290,4 +290,19 @@ internal static class Av1Constants
/// The maximum number of transform units stored for one encoded block.
///
public const int MaxTransformUnitCount = 16;
+
+ ///
+ /// Gets the number of payload bits used by each segmentation feature.
+ ///
+ public static ReadOnlySpan SegmentationFeatureBits => [8, 6, 6, 6, 6, 3, 0, 0];
+
+ ///
+ /// Gets values indicating whether each segmentation feature is signed.
+ ///
+ public static ReadOnlySpan SegmentationFeatureSigned => [1, 1, 1, 1, 1, 0, 0, 0];
+
+ ///
+ /// Gets the maximum magnitude or value permitted for each segmentation feature.
+ ///
+ public static ReadOnlySpan SegmentationFeatureMax => [MaxQ, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, 7, 0, 0];
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs
index 0141302d1..4440307c9 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs
@@ -16,7 +16,7 @@ internal class ObuConstraintDirectionalEnhancementFilterParameters
///
/// Gets or sets the filter damping value.
///
- public int Damping { get; internal set; }
+ public int Damping { get; internal set; } = 3;
///
/// Gets or sets the primary and secondary luma strengths for each filter entry.
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs
index 75ba543b6..fe781baa1 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs
@@ -14,7 +14,7 @@ internal class ObuDeltaParameters
public bool IsPresent { get; internal set; }
///
- /// Gets or sets the delta-value resolution.
+ /// Gets or sets the delta-value multiplier, which is one, two, four, or eight.
///
public int Resolution { get; internal set; }
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
index de8d24528..05e461d60 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
@@ -7,47 +7,32 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
///
-/// Reader for Open Bitstream Units (OBU's).
+/// Parses AV1 open bitstream units and supplies decoded tile payloads to an AV1 tile reader.
///
internal class ObuReader
{
///
- /// Maximum value used for loop filtering.
+ /// The tile reader created for the current coded frame.
///
- private const int MaxLoopFilter = 63;
-
- ///
- /// Number of segments allowed in segmentation map.
- ///
- private const int MaxSegments = 0;
+ private IAv1TileReader? decoder;
///
- /// Number of segment features.
+ /// Gets or sets the most recently parsed sequence header.
///
- private const int SegLvlMax = 8;
+ public ObuSequenceHeader? SequenceHeader { get; set; }
///
- /// Index for reference frame segment feature.
+ /// Gets or sets the frame header associated with the current coded frame.
///
- private const int SegLvlRefFrame = 5;
-
- private const int PrimaryRefNone = 7;
-
- private static readonly int[] SegmentationFeatureBits = [8, 6, 6, 6, 6, 3, 0, 0];
-
- private static readonly int[] SegmentationFeatureSigned = [1, 1, 1, 1, 1, 0, 0, 0];
-
- private static readonly int[] SegmentationFeatureMax = [255, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, 7, 0, 0];
-
- private IAv1TileReader? decoder;
-
- public ObuSequenceHeader? SequenceHeader { get; set; }
-
public ObuFrameHeader? FrameHeader { get; set; }
///
- /// Decode all OBU's in a frame.
+ /// Parses the open bitstream units that make up one coded frame.
///
+ /// The reader positioned at the first OBU.
+ /// The number of bytes available for the coded frame.
+ /// Creates the tile reader when the first tile payload is encountered.
+ /// A value indicating whether each OBU is prefixed by an Annex B length field.
public void ReadAll(ref Av1BitStreamReader reader, int dataSize, Func creator, bool isAnnexB = false)
{
bool seenFrameHeader = false;
@@ -124,6 +109,9 @@ internal class ObuReader
}
this.decoder ??= creator();
+
+ // A combined frame OBU reaches this label after its frame-header portion has
+ // been consumed, leaving the same tile-group syntax as a standalone tile OBU.
this.ReadTileGroup(ref reader, this.decoder, header, out frameDecodingFinished);
if (frameDecodingFinished)
{
@@ -150,8 +138,10 @@ internal class ObuReader
}
///
- /// 5.3.2. OBU header syntax.
+ /// Reads the fixed OBU header and optional extension fields.
///
+ /// The reader positioned at an OBU header.
+ /// The parsed OBU header.
private static ObuHeader ReadObuHeader(ref Av1BitStreamReader reader)
{
ObuHeader header = new();
@@ -188,6 +178,12 @@ internal class ObuReader
return header;
}
+ ///
+ /// Reads an OBU size encoded as an unsigned little-endian base-128 value.
+ ///
+ /// The reader positioned at the size value.
+ /// The decoded OBU size.
+ /// The number of bytes occupied by the encoded size.
private static void ReadObuSize(ref Av1BitStreamReader reader, out int obuSize, out int lengthSize)
{
ulong rawSize = reader.ReadLittleEndianBytes128(out lengthSize);
@@ -200,8 +196,11 @@ internal class ObuReader
}
///
- /// Read OBU header and size.
+ /// Reads an OBU header followed by its optional payload-size field.
///
+ /// The reader positioned at an OBU header.
+ /// The number of bytes occupied by the payload-size field.
+ /// The parsed OBU header and payload size.
private static ObuHeader ReadObuHeaderSize(ref Av1BitStreamReader reader, out int lengthSize)
{
ObuHeader header = ReadObuHeader(ref reader);
@@ -216,8 +215,9 @@ internal class ObuReader
}
///
- /// Check that the trailing bits start with a 1 and end with 0s.
+ /// Reads and validates the trailing one bit followed by zero padding.
///
+ /// The reader positioned at the trailing bits.
/// Consumes a byte, if already byte aligned before the check.
private static void ReadTrailingBits(ref Av1BitStreamReader reader)
{
@@ -230,8 +230,9 @@ internal class ObuReader
}
///
- /// 5.3.5. Byte alignment syntax.
+ /// Consumes zero padding until the reader reaches a byte boundary.
///
+ /// The reader to align.
private static void AlignToByteBoundary(ref Av1BitStreamReader reader)
{
while ((reader.BitPosition & 0x7) > 0)
@@ -244,8 +245,9 @@ internal class ObuReader
}
///
- /// 5.9.9. Compute image size function.
+ /// Computes the mode-information dimensions and stride for the current frame.
///
+ /// The sequence header defining the maximum frame geometry and superblock size.
/// SVT: compute_image_size
private void ComputeImageSize(ObuSequenceHeader sequenceHeader)
{
@@ -256,8 +258,10 @@ internal class ObuReader
}
///
- /// 5.5.1. General sequence header OBU syntax.
+ /// Reads an AV1 sequence-header OBU payload.
///
+ /// The reader positioned at the sequence-header payload.
+ /// The sequence header to populate.
internal static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
{
sequenceHeader.SequenceProfile = (ObuSequenceProfile)reader.ReadLiteral(3);
@@ -368,6 +372,7 @@ internal class ObuReader
{
sequenceHeader.DeltaFrameIdLength = (int)reader.ReadLiteral(4) + 2;
sequenceHeader.AdditionalFrameIdLength = reader.ReadLiteral(3) + 1;
+ sequenceHeader.FrameIdLength = sequenceHeader.DeltaFrameIdLength + (int)sequenceHeader.AdditionalFrameIdLength;
}
// Video related flags removed
@@ -392,7 +397,7 @@ internal class ObuReader
sequenceHeader.EnableInterIntraCompound = reader.ReadBoolean();
sequenceHeader.EnableMaskedCompound = reader.ReadBoolean();
sequenceHeader.EnableWarpedMotion = reader.ReadBoolean();
- sequenceHeader.EnableDualFilter |= reader.ReadBoolean();
+ sequenceHeader.EnableDualFilter = reader.ReadBoolean();
sequenceHeader.EnableOrderHint = reader.ReadBoolean();
if (sequenceHeader.EnableOrderHint)
{
@@ -452,8 +457,11 @@ internal class ObuReader
}
///
- /// 5.5.2. Color config syntax.
+ /// Reads the sequence color configuration.
///
+ /// The reader positioned at the color-configuration syntax.
+ /// The sequence header that determines the permitted color formats.
+ /// The parsed color configuration.
private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
{
ObuColorConfig colorConfig = new();
@@ -492,6 +500,8 @@ internal class ObuReader
colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb &&
colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity)
{
+ // AV1 defines this RGB identity-matrix combination as full-range 4:4:4 and omits
+ // the range and subsampling syntax that other color combinations carry.
colorConfig.ColorRange = true;
colorConfig.SubSamplingX = false;
colorConfig.SubSamplingY = false;
@@ -539,8 +549,10 @@ internal class ObuReader
}
///
- /// 5.5.4. Decoder model info syntax.
+ /// Reads the decoder-model field widths and decoding-clock units.
///
+ /// The reader positioned at the decoder-model syntax.
+ /// The sequence header that receives the decoder-model information.
private static void ReadDecoderModelInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) => sequenceHeader.DecoderModelInfo = new ObuDecoderModelInfo
{
BufferDelayLength = reader.ReadLiteral(5) + 1,
@@ -550,8 +562,10 @@ internal class ObuReader
};
///
- /// 5.5.3. Timing info syntax.
+ /// Reads the sequence timing information.
///
+ /// The reader positioned at the timing-information syntax.
+ /// The sequence header that receives the timing information.
private static void ReadTimingInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
{
sequenceHeader.TimingInfo = new ObuTimingInfo
@@ -567,6 +581,12 @@ internal class ObuReader
}
}
+ ///
+ /// Reads the bit depth permitted by the selected sequence profile.
+ ///
+ /// The reader positioned at the high-bit-depth flag.
+ /// The color configuration that receives the bit depth.
+ /// The sequence header containing the selected profile.
private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader)
{
bool hasHighBitDepth = reader.ReadBoolean();
@@ -585,8 +605,9 @@ internal class ObuReader
}
///
- /// 5.9.8. Superres params syntax.
+ /// Reads the super-resolution parameters and derives the coded frame width.
///
+ /// The reader positioned at the super-resolution syntax.
private void ReadSuperResolutionParameters(ref Av1BitStreamReader reader)
{
ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
@@ -607,6 +628,9 @@ internal class ObuReader
}
frameHeader.FrameSize.SuperResolutionUpscaledWidth = frameHeader.FrameSize.FrameWidth;
+
+ // AV1 signals the upscaled width first. Tile and block decoding use the rounded-down
+ // coded width obtained from the fixed scale numerator and signaled denominator.
frameHeader.FrameSize.FrameWidth =
((frameHeader.FrameSize.SuperResolutionUpscaledWidth * Av1Constants.ScaleNumerator) +
(frameHeader.FrameSize.SuperResolutionDenominator / 2)) /
@@ -622,8 +646,9 @@ internal class ObuReader
}
///
- /// 5.9.6. Render size syntax.
+ /// Reads the optional render dimensions for the current frame.
///
+ /// The reader positioned at the render-size syntax.
private void ReadRenderSize(ref Av1BitStreamReader reader)
{
ObuFrameHeader frameHeader = this.FrameHeader!;
@@ -641,8 +666,10 @@ internal class ObuReader
}
///
- /// 5.9.5. Frame size syntax.
+ /// Reads or derives the current frame dimensions.
///
+ /// The reader positioned at the frame-size syntax.
+ /// A value indicating whether dimensions are signaled instead of inherited from the sequence maximum.
private void ReadFrameSize(ref Av1BitStreamReader reader, bool frameSizeOverrideFlag)
{
ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
@@ -663,8 +690,12 @@ internal class ObuReader
}
///
- /// 5.9.15. Tile info syntax.
+ /// Reads the tile layout and derives tile boundaries in mode-information units.
///
+ /// The reader positioned at the tile-information syntax.
+ /// The sequence header defining superblock geometry.
+ /// The frame header defining the current frame geometry.
+ /// The parsed tile layout.
private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
ObuTileGroupHeader tileInfo = new();
@@ -677,6 +708,8 @@ internal class ObuReader
int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (superblockSizeLog2 << 1);
+ // The bitstream constrains tile dimensions in superblocks, while the decoder stores
+ // boundaries in mode-information units for direct use during block traversal.
tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superblockSizeLog2;
tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superblockSizeLog2;
tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperblock, superblockColumnCount);
@@ -816,18 +849,18 @@ internal class ObuReader
}
///
- /// 5.9.2. Uncompressed header syntax.
+ /// Reads the uncompressed syntax for the current still-image frame.
///
+ /// The reader positioned at the uncompressed frame header.
private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader)
{
ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
ObuFrameHeader frameHeader = this.FrameHeader!;
- int idLength = 0;
+ int idLength = sequenceHeader.FrameIdLength;
uint previousFrameId = 0;
bool frameSizeOverrideFlag = false;
if (sequenceHeader.IsFrameIdNumbersPresent)
{
- idLength = sequenceHeader.FrameIdLength - 1 + sequenceHeader.DeltaFrameIdLength - 2 + 3;
DebugGuard.MustBeLessThanOrEqualTo(idLength, 16, nameof(idLength));
}
@@ -898,15 +931,18 @@ internal class ObuReader
}
frameHeader.DisableCdfUpdate = reader.ReadBoolean();
- frameHeader.AllowScreenContentTools = sequenceHeader.ForceScreenContentTools == 2;
- if (frameHeader.AllowScreenContentTools)
+ if (sequenceHeader.ForceScreenContentTools == 2)
{
frameHeader.AllowScreenContentTools = reader.ReadBoolean();
}
+ else
+ {
+ frameHeader.AllowScreenContentTools = sequenceHeader.ForceScreenContentTools != 0;
+ }
if (frameHeader.AllowScreenContentTools)
{
- if (sequenceHeader.ForceIntegerMotionVector == 1)
+ if (sequenceHeader.ForceIntegerMotionVector == 2)
{
frameHeader.ForceIntegerMotionVector = reader.ReadBoolean();
}
@@ -1092,49 +1128,13 @@ internal class ObuReader
throw new NotImplementedException();
}
- int tilesCount = frameHeader.TilesInfo.TileColumnCount * frameHeader.TilesInfo.TileRowCount;
- frameHeader.CodedLossless = true;
- frameHeader.SegmentationParameters.QMLevel[0] = new int[Av1Constants.MaxSegmentCount];
- frameHeader.SegmentationParameters.QMLevel[1] = new int[Av1Constants.MaxSegmentCount];
- frameHeader.SegmentationParameters.QMLevel[2] = new int[Av1Constants.MaxSegmentCount];
- for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++)
- {
- int qIndex = Av1QuantizationLookup.GetQIndex(frameHeader.SegmentationParameters, segmentId, frameHeader.QuantizationParameters.BaseQIndex);
- frameHeader.QuantizationParameters.QIndex[segmentId] = qIndex;
- frameHeader.LosslessArray[segmentId] = qIndex == 0 &&
- frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.Y] == 0 &&
- frameHeader.QuantizationParameters.DeltaQAc[(int)Av1Plane.U] == 0 &&
- frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.U] == 0 &&
- frameHeader.QuantizationParameters.DeltaQAc[(int)Av1Plane.V] == 0 &&
- frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.V] == 0;
- if (!frameHeader.LosslessArray[segmentId])
- {
- frameHeader.CodedLossless = false;
- }
-
- if (frameHeader.QuantizationParameters.IsUsingQMatrix)
- {
- if (frameHeader.LosslessArray[segmentId])
- {
- frameHeader.SegmentationParameters.QMLevel[0][segmentId] = 15;
- frameHeader.SegmentationParameters.QMLevel[1][segmentId] = 15;
- frameHeader.SegmentationParameters.QMLevel[2][segmentId] = 15;
- }
- else
- {
- frameHeader.SegmentationParameters.QMLevel[0][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.Y];
- frameHeader.SegmentationParameters.QMLevel[1][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.U];
- frameHeader.SegmentationParameters.QMLevel[2][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.V];
- }
- }
- }
+ Av1QuantizationLookup.UpdateFrameQuantizationState(frameHeader);
if (frameHeader.CodedLossless)
{
DebugGuard.IsFalse(frameHeader.DeltaQParameters.IsPresent, nameof(frameHeader.DeltaQParameters.IsPresent), "No Delta Q parameters are allowed for lossless frame.");
}
- frameHeader.AllLossless = frameHeader.CodedLossless && frameHeader.FrameSize.FrameWidth == frameHeader.FrameSize.SuperResolutionUpscaledWidth;
this.ReadLoopFilterParameters(ref reader, sequenceHeader);
ReadCdefParameters(ref reader, sequenceHeader, frameHeader);
ReadLoopRestorationParameters(ref reader, sequenceHeader, frameHeader);
@@ -1156,12 +1156,22 @@ internal class ObuReader
frameHeader.FilmGrainParameters = ReadFilmGrainFilterParameters(ref reader, sequenceHeader, frameHeader);
}
+ ///
+ /// Determines whether segmentation and a specific per-segment feature are both enabled.
+ ///
+ /// The frame segmentation state.
+ /// The segment identifier.
+ /// The feature to inspect.
+ /// when the feature is active; otherwise, .
private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature)
=> segmentationParameters.Enabled && segmentationParameters.IsFeatureActive(segmentId, feature);
///
- /// 5.9.1. General frame header OBU syntax.
+ /// Reads an AV1 frame header and removes its byte length from the remaining OBU payload size.
///
+ /// The reader positioned at the frame-header payload.
+ /// The OBU header whose remaining payload size is updated.
+ /// A value indicating whether trailing-bit syntax follows the frame header.
internal void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit)
{
int startBitPosition = reader.BitPosition;
@@ -1179,8 +1189,12 @@ internal class ObuReader
}
///
- /// 5.11.1. General tile group OBU syntax.
+ /// Reads a tile-group header and passes each contained tile payload to the tile reader.
///
+ /// The reader positioned at the tile-group payload.
+ /// The tile reader that decodes each tile payload.
+ /// The OBU header containing the remaining tile-group payload size.
+ /// Receives whether this group contains the final tile of the frame.
private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileReader decoder, ObuHeader header, out bool isLastTileGroup)
{
ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
@@ -1225,6 +1239,8 @@ internal class ObuReader
frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.U].Type != ObuRestorationType.None ||
frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.V].Type != ObuRestorationType.None);
+ // All tile sizes except the final size are explicitly stored as size-minus-one. The
+ // last tile consumes the bytes that remain in the OBU payload.
for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++)
{
bool isLastTile = tileNum == tileGroupEnd;
@@ -1248,8 +1264,10 @@ internal class ObuReader
}
///
- /// 5.9.13. Delta quantizer syntax.
+ /// Reads an optional signed quantizer-index delta.
///
+ /// The reader positioned at a delta-quantizer field.
+ /// The decoded delta, or zero when the field is absent.
private static int ReadDeltaQ(ref Av1BitStreamReader reader)
{
int deltaQ = 0;
@@ -1262,11 +1280,13 @@ internal class ObuReader
}
///
- /// 5.9.17. Quantizer index delta parameters syntax.
+ /// Reads the frame-level delta-quantizer configuration.
///
+ /// The reader positioned at the delta-quantizer parameters.
+ /// The frame header that receives the parameters.
private static void ReadFrameDeltaQParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
{
- frameHeader.DeltaQParameters.Resolution = 0;
+ frameHeader.DeltaQParameters.Resolution = 1;
frameHeader.DeltaQParameters.IsPresent = false;
if (frameHeader.QuantizationParameters.BaseQIndex > 0)
{
@@ -1275,17 +1295,19 @@ internal class ObuReader
if (frameHeader.DeltaQParameters.IsPresent)
{
- frameHeader.DeltaQParameters.Resolution = (int)reader.ReadLiteral(2);
+ frameHeader.DeltaQParameters.Resolution = 1 << (int)reader.ReadLiteral(2);
}
}
///
- /// 5.9.18. Loop filter delta parameters syntax.
+ /// Reads the frame-level delta-loop-filter configuration.
///
+ /// The reader positioned at the delta-loop-filter parameters.
+ /// The frame header that receives the parameters.
private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
{
frameHeader.DeltaLoopFilterParameters.IsPresent = false;
- frameHeader.DeltaLoopFilterParameters.Resolution = 0;
+ frameHeader.DeltaLoopFilterParameters.Resolution = 1;
frameHeader.DeltaLoopFilterParameters.IsMulti = false;
if (frameHeader.DeltaQParameters.IsPresent)
{
@@ -1296,15 +1318,18 @@ internal class ObuReader
if (frameHeader.DeltaLoopFilterParameters.IsPresent)
{
- frameHeader.DeltaLoopFilterParameters.Resolution = (int)reader.ReadLiteral(2);
+ frameHeader.DeltaLoopFilterParameters.Resolution = 1 << (int)reader.ReadLiteral(2);
frameHeader.DeltaLoopFilterParameters.IsMulti = reader.ReadBoolean();
}
}
}
///
- /// 5.9.12. Quantization params syntax.
+ /// Reads the base index, plane deltas, and optional quantization matrices for a frame.
///
+ /// The reader positioned at the quantization parameters.
+ /// The sequence header defining the active color planes.
+ /// The frame header that receives the quantization parameters.
private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
ObuQuantizationParameters quantParams = frameHeader.QuantizationParameters;
@@ -1364,15 +1389,17 @@ internal class ObuReader
}
///
- /// 5.9.14. Segmentation params syntax.
+ /// Reads the segmentation map controls and per-segment feature values.
///
+ /// The reader positioned at the segmentation parameters.
+ /// The frame header that receives the segmentation state.
private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
{
frameHeader.SegmentationParameters.Enabled = reader.ReadBoolean();
if (frameHeader.SegmentationParameters.Enabled)
{
- if (frameHeader.PrimaryReferenceFrame == PrimaryRefNone)
+ if (frameHeader.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone)
{
frameHeader.SegmentationParameters.SegmentationUpdateMap = 1;
frameHeader.SegmentationParameters.SegmentationTemporalUpdate = 0;
@@ -1391,9 +1418,9 @@ internal class ObuReader
if (frameHeader.SegmentationParameters.SegmentationUpdateData == 1)
{
- for (int i = 0; i < MaxSegments; i++)
+ for (int i = 0; i < Av1Constants.MaxSegmentCount; i++)
{
- for (int j = 0; j < SegLvlMax; j++)
+ for (int j = 0; j < Av1Constants.SegmentationLevelMax; j++)
{
int featureValue = 0;
bool featureEnabled = reader.ReadBoolean();
@@ -1401,9 +1428,9 @@ internal class ObuReader
int clippedValue = 0;
if (featureEnabled)
{
- int bitsToRead = SegmentationFeatureBits[j];
- int limit = SegmentationFeatureMax[j];
- if (SegmentationFeatureSigned[j] == 1)
+ int bitsToRead = Av1Constants.SegmentationFeatureBits[j];
+ int limit = Av1Constants.SegmentationFeatureMax[j];
+ if (Av1Constants.SegmentationFeatureSigned[j] == 1)
{
featureValue = reader.ReadSignedFromUnsigned(1 + bitsToRead);
clippedValue = Av1Math.Clip3(-limit, limit, featureValue);
@@ -1411,6 +1438,7 @@ internal class ObuReader
else
{
featureValue = (int)reader.ReadLiteral(bitsToRead);
+ clippedValue = featureValue;
}
}
@@ -1440,7 +1468,7 @@ internal class ObuReader
if (frameHeader.SegmentationParameters.FeatureEnabled[i, j])
{
frameHeader.SegmentationParameters.LastActiveSegmentId = i;
- if (j >= SegLvlRefFrame)
+ if (j >= (int)ObuSegmentationLevelFeature.ReferenceFrame)
{
frameHeader.SegmentationParameters.SegmentIdPrecedesSkip = true;
}
@@ -1450,8 +1478,10 @@ internal class ObuReader
}
///
- /// 5.9.11. Loop filter params syntax
+ /// Reads the deblocking-loop-filter levels and optional reference and mode deltas.
///
+ /// The reader positioned at the loop-filter parameters.
+ /// The sequence header defining the active color planes.
private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
{
ObuFrameHeader frameHeader = this.FrameHeader!;
@@ -1499,8 +1529,10 @@ internal class ObuReader
}
///
- /// 5.9.21. TX mode syntax.
+ /// Reads or derives the transform-size selection mode.
///
+ /// The reader positioned at the transform-mode flag.
+ /// The frame header that receives the transform mode.
private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
{
if (frameHeader.CodedLossless)
@@ -1521,8 +1553,11 @@ internal class ObuReader
}
///
- /// See section 5.9.20. Loop restoration params syntax.
+ /// Reads the loop-restoration type and restoration-unit size for each plane.
///
+ /// The reader positioned at the loop-restoration parameters.
+ /// The sequence header defining restoration availability and color planes.
+ /// The frame header that receives the restoration parameters.
private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration)
@@ -1535,6 +1570,8 @@ internal class ObuReader
int planesCount = sequenceHeader.ColorConfig.PlaneCount;
for (int i = 0; i < planesCount; i++)
{
+ // The AV1 frame syntax orders its two restoration bits as none, switchable,
+ // Wiener, and self-guided projection, matching ObuRestorationType values.
frameHeader.LoopRestorationParameters.Items[i].Type = (ObuRestorationType)reader.ReadLiteral(2);
if (frameHeader.LoopRestorationParameters.Items[i].Type != ObuRestorationType.None)
@@ -1575,13 +1612,16 @@ internal class ObuReader
}
///
- /// See section 5.9.19. CDEF params syntax.
+ /// Reads constrained directional enhancement filter strengths for the active planes.
///
+ /// The reader positioned at the CDEF parameters.
+ /// The sequence header defining CDEF availability and color planes.
+ /// The frame header that receives the CDEF parameters.
private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
ObuConstraintDirectionalEnhancementFilterParameters cdefInfo = frameHeader.CdefParameters;
bool multiPlane = sequenceHeader.ColorConfig.PlaneCount > 1;
- if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || sequenceHeader.CdefLevel == 0)
+ if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableCdef)
{
cdefInfo.BitCount = 0;
cdefInfo.YStrength[0] = 0;
@@ -1606,8 +1646,11 @@ internal class ObuReader
}
///
- /// 5.9.24. Global motion params syntax.
+ /// Reads global-motion parameters when permitted by the frame type.
///
+ /// The reader positioned at the global-motion parameters.
+ /// The sequence header controlling global-motion tools.
+ /// The current frame header.
private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
_ = reader;
@@ -1623,8 +1666,11 @@ internal class ObuReader
}
///
- /// 5.9.23. Frame reference mode syntax
+ /// Reads or derives the reference prediction mode.
///
+ /// The reader positioned at the reference-mode flag.
+ /// The current frame header.
+ /// The frame reference mode.
private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
{
if (frameHeader.IsIntra)
@@ -1636,11 +1682,14 @@ internal class ObuReader
}
///
- /// 5.11.10. Skip mode syntax.
+ /// Reads skip-mode enablement when the frame is eligible to use it.
///
+ /// The reader positioned at the skip-mode syntax.
+ /// The sequence header controlling order hints.
+ /// The frame header that receives the skip-mode state.
private static void ReadSkipModeParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
- if (frameHeader.IsIntra || frameHeader.ReferenceMode == ObuReferenceMode.ReferenceModeSelect || !sequenceHeader.OrderHintInfo.EnableOrderHint)
+ if (frameHeader.IsIntra || frameHeader.ReferenceMode == ObuReferenceMode.SingleReference || !sequenceHeader.OrderHintInfo.EnableOrderHint)
{
frameHeader.SkipModeParameters.SkipModeAllowed = false;
}
@@ -1660,8 +1709,12 @@ internal class ObuReader
}
///
- /// 5.9.30. Film grain params syntax.
+ /// Reads film-grain synthesis parameters for the current displayed frame.
///
+ /// The reader positioned at the film-grain parameters.
+ /// The sequence header defining film-grain availability and color sampling.
+ /// The current frame header.
+ /// The parsed film-grain parameters.
private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
ObuFilmGrainParameters grainParams = new();
@@ -1684,7 +1737,9 @@ internal class ObuReader
}
else
{
- grainParams.UpdateGrain = false;
+ // Only inter frames can inherit parameters from a reference frame. Still-image
+ // intra frames always carry a complete parameter set when grain is enabled.
+ grainParams.UpdateGrain = true;
}
if (!grainParams.UpdateGrain)
@@ -1737,7 +1792,7 @@ internal class ObuReader
grainParams.NumCrPoints = reader.ReadLiteral(4);
grainParams.PointCrValue = new uint[grainParams.NumCrPoints];
grainParams.PointCrScaling = new uint[grainParams.NumCrPoints];
- for (int i = 0; i < grainParams.NumCbPoints; i++)
+ for (int i = 0; i < grainParams.NumCrPoints; i++)
{
grainParams.PointCrValue[i] = reader.ReadLiteral(8);
grainParams.PointCrScaling[i] = reader.ReadLiteral(8);
@@ -1803,12 +1858,20 @@ internal class ObuReader
return grainParams;
}
+ ///
+ /// Determines whether a sequence-level index is assigned by the AV1 specification.
+ ///
+ /// The sequence-level index.
+ /// for assigned indices; otherwise, .
private static bool IsValidSequenceLevel(int sequenceLevelIndex)
=> sequenceLevelIndex is < 24 or 31;
///
- /// Returns the smallest value for k such that blockSize << k is greater than or equal to target.
+ /// Returns the smallest shift for which shifted left reaches .
///
+ /// The initial block count.
+ /// The minimum shifted value.
+ /// The required base-2 shift.
public static int TileLog2(int blockSize, int target)
{
int k;
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs
index 0fa97ada0..c1b522b41 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs
@@ -125,11 +125,6 @@ internal class ObuSequenceHeader
///
public int SuperblockSizeLog2 { get; private set; }
- ///
- /// Gets or sets the filter-intra capability level.
- ///
- public int FilterIntraLevel { get; set; }
-
///
/// Gets or sets a value indicating whether intra edge filtering is enabled.
///
@@ -143,7 +138,16 @@ internal class ObuSequenceHeader
///
/// Gets or sets a value indicating whether order hints are enabled.
///
- public bool EnableOrderHint { get; set; }
+ public bool EnableOrderHint
+ {
+ get => this.OrderHintInfo.EnableOrderHint;
+ set
+ {
+ // Order-hint availability is consumed through OrderHintInfo by frame parsing, so
+ // keep the sequence-facing flag and dependent tool state synchronized.
+ this.OrderHintInfo.EnableOrderHint = value;
+ }
+ }
///
/// Gets or sets a value indicating whether inter-intra compound prediction is enabled.
@@ -180,11 +184,6 @@ internal class ObuSequenceHeader
///
public bool EnableSuperResolution { get; set; }
- ///
- /// Gets or sets the constrained directional enhancement filter capability level.
- ///
- public int CdefLevel { get; set; }
-
///
/// Gets or sets a value indicating whether loop restoration is enabled.
///
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
index 9cc445968..0a27fad92 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
@@ -1,22 +1,33 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+///
+/// Writes the AV1 open bitstream units required for a single still-image frame.
+///
internal class ObuWriter
{
- private int[] previousQIndex = [];
- private int[] previousDeltaLoopFilter = [];
-
///
- /// Encode a single frame into OBU's.
+ /// Writes a temporal delimiter and the supplied sequence and frame OBUs.
///
+ /// The configuration used to allocate temporary encoding memory.
+ /// The destination stream.
+ /// The optional still-picture sequence header.
+ /// The optional intra-frame header.
+ /// The tile writer used when a frame header is supplied.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage(
+ "Performance",
+ "CA1822:Mark members as static",
+ Justification = "Preserves the existing writer instance contract.")]
public void WriteAll(Configuration configuration, Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1TileWriter tileWriter)
{
- // TODO: Determine inital size dynamically
+ // The allocation expands when necessary; this initial size avoids repeated growth for
+ // the small headers and tiles produced by the current still-image encoder.
int initialBufferSize = 2000;
AutoExpandingMemory buffer = new(configuration, initialBufferSize);
Av1BitStreamWriter writer = new(buffer);
@@ -32,7 +43,7 @@ internal class ObuWriter
if (frameHeader != null && sequenceHeader != null)
{
- this.WriteFrameHeader(ref writer, sequenceHeader, frameHeader, false);
+ WriteFrameHeader(ref writer, sequenceHeader, frameHeader);
if (frameHeader.TilesInfo != null)
{
WriteTileGroup(ref writer, frameHeader.TilesInfo, tileWriter);
@@ -44,27 +55,24 @@ internal class ObuWriter
}
}
- private static void WriteObuHeader(ref Av1BitStreamWriter writer, ObuType type)
+ ///
+ /// Creates a byte-aligned OBU header with an explicit payload-size field and no extension.
+ ///
+ /// The OBU payload type.
+ /// The encoded OBU header byte.
+ private static byte WriteObuHeader(ObuType type)
{
- writer.WriteBoolean(false); // Forbidden bit
- writer.WriteLiteral((uint)type, 4);
- writer.WriteBoolean(false); // Extension
- writer.WriteBoolean(true); // HasSize
- writer.WriteBoolean(false); // Reserved
+ // The only set fields are the four-bit type and the has-size flag; forbidden,
+ // extension, and reserved bits remain zero.
+ return (byte)(((byte)type << 3) | 0x02);
}
- private static byte WriteObuHeader(ObuType type) =>
-
- // 0: Forbidden bit
- // 1: Type, 4
- // 5: Extension (false)
- // 6: HasSize (true)
- // 7: Reserved (false)
- (byte)(((byte)type << 3) | 0x02);
-
///
- /// Read OBU header and size.
+ /// Writes a complete byte-aligned OBU with a little-endian base-128 payload size.
///
+ /// The destination stream.
+ /// The OBU payload type.
+ /// The complete OBU payload.
private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span payload)
{
stream.WriteByte(WriteObuHeader(type));
@@ -77,18 +85,20 @@ internal class ObuWriter
}
///
- /// Write trsainling bits to end on a byte boundary, these trailing bits start with a 1 and end with 0s.
+ /// Writes a trailing one bit followed by enough zero bits to reach a byte boundary.
///
- /// Write an additional byte, if already byte aligned before.
+ /// The bit writer receiving the trailing bits.
+ /// Writes an additional byte when the writer is already byte aligned.
private static void WriteTrailingBits(ref Av1BitStreamWriter writer)
{
int bitsBeforeAlignment = 8 - (writer.BitPosition & 0x7);
- if (bitsBeforeAlignment != 8)
- {
- writer.WriteLiteral(1U << (bitsBeforeAlignment - 1), bitsBeforeAlignment);
- }
+ writer.WriteLiteral(1U << (bitsBeforeAlignment - 1), bitsBeforeAlignment);
}
+ ///
+ /// Writes zero padding until the output reaches a byte boundary.
+ ///
+ /// The bit writer to align.
private static void AlignToByteBoundary(ref Av1BitStreamWriter writer)
{
while ((writer.BitPosition & 0x7) > 0)
@@ -97,14 +107,11 @@ internal class ObuWriter
}
}
- private static bool IsValidObuType(ObuType type) => type switch
- {
- ObuType.SequenceHeader or ObuType.TemporalDelimiter or ObuType.FrameHeader or
- ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrameHeader or
- ObuType.TileList or ObuType.Padding => true,
- _ => false,
- };
-
+ ///
+ /// Writes a reduced still-picture sequence header.
+ ///
+ /// The bit writer receiving the sequence header.
+ /// The sequence header to encode.
private static void WriteSequenceHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader)
{
writer.WriteLiteral((uint)sequenceHeader.SequenceProfile, 3);
@@ -132,6 +139,11 @@ internal class ObuWriter
WriteTrailingBits(ref writer);
}
+ ///
+ /// Writes the sequence color configuration.
+ ///
+ /// The bit writer receiving the color configuration.
+ /// The sequence header containing the color configuration.
private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader)
{
ObuColorConfig colorConfig = sequenceHeader.ColorConfig;
@@ -159,6 +171,8 @@ internal class ObuWriter
colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb &&
colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity)
{
+ // AV1 fixes this RGB identity-matrix combination to full-range 4:4:4 and omits
+ // the range and subsampling fields used by YUV configurations.
colorConfig.ColorRange = true;
colorConfig.SubSamplingX = false;
colorConfig.SubSamplingY = false;
@@ -184,6 +198,12 @@ internal class ObuWriter
writer.WriteBoolean(colorConfig.HasSeparateUvDelta);
}
+ ///
+ /// Writes the profile-dependent bit-depth flags.
+ ///
+ /// The bit writer receiving the flags.
+ /// The color configuration containing the bit depth.
+ /// The sequence header containing the selected profile.
private static void WriteBitDepth(ref Av1BitStreamWriter writer, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader)
{
bool hasHighBitDepth = colorConfig.BitDepth > Av1BitDepth.EightBit;
@@ -194,9 +214,17 @@ internal class ObuWriter
}
}
+ ///
+ /// Writes the super-resolution enablement and scale denominator.
+ ///
+ /// The bit writer receiving the super-resolution syntax.
+ /// The sequence header controlling super-resolution availability.
+ /// The frame header containing the scale denominator.
private static void WriteSuperResolutionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
- bool useSuperResolution = false;
+ bool useSuperResolution = sequenceHeader.EnableSuperResolution &&
+ frameHeader.FrameSize.SuperResolutionDenominator != Av1Constants.ScaleNumerator;
+
if (sequenceHeader.EnableSuperResolution)
{
writer.WriteBoolean(useSuperResolution);
@@ -208,10 +236,18 @@ internal class ObuWriter
}
}
+ ///
+ /// Writes the render-size override when display dimensions differ from the upscaled frame.
+ ///
+ /// The bit writer receiving the render-size syntax.
+ /// The frame header containing coded and render dimensions.
private static void WriteRenderSize(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
- bool renderSizeAndFrameSizeDifferent = false;
- writer.WriteBoolean(false);
+ bool renderSizeAndFrameSizeDifferent =
+ frameHeader.FrameSize.RenderWidth != frameHeader.FrameSize.SuperResolutionUpscaledWidth ||
+ frameHeader.FrameSize.RenderHeight != frameHeader.FrameSize.FrameHeight;
+
+ writer.WriteBoolean(renderSizeAndFrameSizeDifferent);
if (renderSizeAndFrameSizeDifferent)
{
writer.WriteLiteral((uint)frameHeader.FrameSize.RenderWidth - 1, 16);
@@ -219,41 +255,30 @@ internal class ObuWriter
}
}
- private static void WriteFrameSizeWithReferences(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool frameSizeOverrideFlag)
- {
- bool foundReference = false;
- for (int i = 0; i < Av1Constants.ReferencesPerFrame; i++)
- {
- writer.WriteBoolean(foundReference);
- if (foundReference)
- {
- // Take values over from reference frame
- break;
- }
- }
-
- if (!foundReference)
- {
- WriteFrameSize(ref writer, sequenceHeader, frameHeader, frameSizeOverrideFlag);
- WriteRenderSize(ref writer, frameHeader);
- }
- else
- {
- WriteSuperResolutionParameters(ref writer, sequenceHeader, frameHeader);
- }
- }
-
+ ///
+ /// Writes an optional frame-size override followed by super-resolution syntax.
+ ///
+ /// The bit writer receiving the frame size.
+ /// The sequence header defining dimension field widths.
+ /// The frame header containing the dimensions.
+ /// A value indicating whether explicit dimensions are written.
private static void WriteFrameSize(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool frameSizeOverrideFlag)
{
if (frameSizeOverrideFlag)
{
- writer.WriteLiteral((uint)frameHeader.FrameSize.FrameWidth - 1, sequenceHeader.FrameWidthBits + 1);
- writer.WriteLiteral((uint)frameHeader.FrameSize.FrameHeight - 1, sequenceHeader.FrameHeightBits + 1);
+ writer.WriteLiteral((uint)frameHeader.FrameSize.FrameWidth - 1, sequenceHeader.FrameWidthBits);
+ writer.WriteLiteral((uint)frameHeader.FrameSize.FrameHeight - 1, sequenceHeader.FrameHeightBits);
}
WriteSuperResolutionParameters(ref writer, sequenceHeader, frameHeader);
}
+ ///
+ /// Writes the frame tile layout.
+ ///
+ /// The bit writer receiving the tile information.
+ /// The sequence header defining superblock geometry.
+ /// The frame header containing tile boundaries.
private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
ObuTileGroupHeader tileInfo = frameHeader.TilesInfo;
@@ -273,8 +298,10 @@ internal class ObuWriter
tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superblockRowCount, Av1Constants.MaxTileRowCount));
tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, ObuReader.TileLog2(maxTileAreaOfSuperBlock, superblockColumnCount * superblockRowCount));
- int log2TileColumnCount = Av1Math.Log2(tileInfo.TileColumnCount);
- int log2TileRowCount = Av1Math.Log2(tileInfo.TileRowCount);
+ int log2TileColumnCount = ObuReader.TileLog2(1, tileInfo.TileColumnCount);
+ int log2TileRowCount = ObuReader.TileLog2(1, tileInfo.TileRowCount);
+ tileInfo.TileColumnCountLog2 = log2TileColumnCount;
+ tileInfo.TileRowCountLog2 = log2TileRowCount;
writer.WriteBoolean(tileInfo.HasUniformTileSpacing);
if (tileInfo.HasUniformTileSpacing)
@@ -293,7 +320,7 @@ internal class ObuWriter
}
// rows
- tileInfo.MinLog2TileRowCount = Math.Min(tileInfo.MinLog2TileCount - log2TileColumnCount, 0);
+ tileInfo.MinLog2TileRowCount = Math.Max(tileInfo.MinLog2TileCount - log2TileColumnCount, 0);
ones = log2TileRowCount - tileInfo.MinLog2TileRowCount;
while (ones-- > 0)
{
@@ -346,11 +373,14 @@ internal class ObuWriter
frameHeader.TilesInfo = tileInfo;
}
- private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ ///
+ /// Writes the reduced uncompressed header for an intra still-image frame.
+ ///
+ /// The bit writer receiving the uncompressed frame header.
+ /// The sequence header controlling available coding tools.
+ /// The frame header to encode.
+ private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
- // TODO: Make tile count configurable.
- int tileCount = 1;
- int planesCount = sequenceHeader.ColorConfig.PlaneCount;
writer.WriteBoolean(frameHeader.DisableCdfUpdate);
if (sequenceHeader.ForceScreenContentTools == 2)
{
@@ -410,19 +440,15 @@ internal class ObuWriter
WriteTileInfo(ref writer, sequenceHeader, frameHeader);
WriteQuantizationParameters(ref writer, sequenceHeader, frameHeader);
- WriteSegmentationParameters(ref writer, sequenceHeader, frameHeader);
+ WriteSegmentationParameters(ref writer, frameHeader);
+ Av1QuantizationLookup.UpdateFrameQuantizationState(frameHeader);
if (frameHeader.QuantizationParameters.BaseQIndex > 0)
{
writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent);
if (frameHeader.DeltaQParameters.IsPresent)
{
- writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution - 1, 2);
- this.previousQIndex = new int[tileCount];
- for (int tileIndex = 0; tileIndex < tileCount; tileIndex++)
- {
- this.previousQIndex[tileIndex] = frameHeader.QuantizationParameters.BaseQIndex;
- }
+ writer.WriteLiteral((uint)Av1Math.MostSignificantBit((uint)frameHeader.DeltaQParameters.Resolution), 2);
if (frameHeader.AllowIntraBlockCopy)
{
@@ -438,28 +464,18 @@ internal class ObuWriter
if (frameHeader.DeltaLoopFilterParameters.IsPresent)
{
- writer.WriteLiteral((uint)(1 + Av1Math.MostSignificantBit((uint)frameHeader.DeltaLoopFilterParameters.Resolution) - 1), 2);
+ writer.WriteLiteral((uint)Av1Math.MostSignificantBit((uint)frameHeader.DeltaLoopFilterParameters.Resolution), 2);
writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti);
- int frameLoopFilterCount = sequenceHeader.ColorConfig.IsMonochrome ? Av1Constants.FrameLoopFilterCount - 2 : Av1Constants.FrameLoopFilterCount;
- this.previousDeltaLoopFilter = new int[frameLoopFilterCount];
- for (int loopFilterId = 0; loopFilterId < frameLoopFilterCount; loopFilterId++)
- {
- this.previousDeltaLoopFilter[loopFilterId] = 0;
- }
}
}
}
- if (frameHeader.AllLossless)
- {
- throw new NotImplementedException("No entire lossless supported.");
- }
- else
+ if (!frameHeader.AllLossless)
{
if (!frameHeader.CodedLossless)
{
WriteLoopFilterParameters(ref writer, sequenceHeader, frameHeader);
- if (sequenceHeader.CdefLevel > 0)
+ if (sequenceHeader.EnableCdef)
{
WriteCdefParameters(ref writer, sequenceHeader, frameHeader);
}
@@ -485,38 +501,34 @@ internal class ObuWriter
WriteFilmGrainFilterParameters(ref writer, sequenceHeader, frameHeader);
}
- private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature)
- => segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature];
-
- private int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool writeTrailingBits)
+ ///
+ /// Writes the frame-header portion of a combined frame OBU.
+ ///
+ /// The bit writer receiving the frame header.
+ /// The sequence header controlling available coding tools.
+ /// The frame header to encode.
+ private static void WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
- int startBitPosition = writer.BitPosition;
- this.WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameHeader);
- if (writeTrailingBits)
- {
- WriteTrailingBits(ref writer);
- }
-
- int endPosition = writer.BitPosition;
- int headerBytes = (endPosition - startBitPosition) / 8;
- return headerBytes;
+ WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameHeader);
}
///
- /// 5.11.1. General tile group OBU syntax.
+ /// Writes a tile-group header and all tile payloads for a combined frame OBU.
///
- private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter)
+ /// The bit writer receiving the tile group.
+ /// The frame tile layout.
+ /// The writer that produces each entropy-coded tile payload.
+ private static void WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter)
{
int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
- int startBitPosition = writer.BitPosition;
- bool tileStartAndEndPresentFlag = tileCount > 1;
- writer.WriteBoolean(tileStartAndEndPresentFlag);
-
- uint tileGroupStart = 0U;
- uint tileGroupEnd = (uint)tileCount - 1U;
- if (tileCount != 1)
- {
- int tileBits = Av1Math.Log2(tileInfo.TileColumnCount) + Av1Math.Log2(tileInfo.TileRowCount);
+ if (tileCount > 1)
+ {
+ // This writer places every tile in one group, so the optional range spans the
+ // complete frame whenever the range syntax is present.
+ writer.WriteBoolean(true);
+ uint tileGroupStart = 0U;
+ uint tileGroupEnd = (uint)tileCount - 1U;
+ int tileBits = tileInfo.TileColumnCountLog2 + tileInfo.TileRowCountLog2;
writer.WriteLiteral(tileGroupStart, tileBits);
writer.WriteLiteral(tileGroupEnd, tileBits);
}
@@ -524,12 +536,14 @@ internal class ObuWriter
AlignToByteBoundary(ref writer);
WriteTileData(ref writer, tileInfo, tileWriter);
-
- int endBitPosition = writer.BitPosition;
- int headerBytes = (endBitPosition - startBitPosition) / 8;
- return headerBytes;
}
+ ///
+ /// Writes the size-prefixed tile payloads in raster order.
+ ///
+ /// The byte-aligned bit writer receiving tile data.
+ /// The frame tile layout and tile-size field width.
+ /// The writer that produces each tile payload.
private static void WriteTileData(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter)
{
int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
@@ -545,7 +559,12 @@ internal class ObuWriter
}
}
- private static int WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ)
+ ///
+ /// Writes an optional signed quantizer-index delta.
+ ///
+ /// The bit writer receiving the delta.
+ /// The quantizer-index delta.
+ private static void WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ)
{
bool isCoded = deltaQ != 0;
writer.WriteBoolean(isCoded);
@@ -553,43 +572,14 @@ internal class ObuWriter
{
writer.WriteSignedFromUnsigned(deltaQ, 7);
}
-
- return deltaQ;
- }
-
- private static void WriteFrameDeltaQParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
- {
- if (frameHeader.QuantizationParameters.BaseQIndex > 0)
- {
- writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent);
- }
-
- if (frameHeader.DeltaQParameters.IsPresent)
- {
- writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution, 2);
- }
- }
-
- private static void WriteFrameDeltaLoopFilterParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
- {
- if (frameHeader.DeltaQParameters.IsPresent)
- {
- if (!frameHeader.AllowIntraBlockCopy)
- {
- writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsPresent);
- }
-
- if (frameHeader.DeltaLoopFilterParameters.IsPresent)
- {
- writer.WriteLiteral((uint)frameHeader.DeltaLoopFilterParameters.Resolution, 2);
- writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti);
- }
- }
}
///
- /// See section 5.9.12.
+ /// Writes the base index, plane deltas, and optional quantization matrices for a frame.
///
+ /// The bit writer receiving the quantization parameters.
+ /// The sequence header defining active color planes.
+ /// The frame header containing the quantization parameters.
private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
ObuQuantizationParameters quantParams = frameHeader.QuantizationParameters;
@@ -623,16 +613,54 @@ internal class ObuWriter
}
}
- private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ ///
+ /// Writes segmentation feature data for an independently decoded still-image frame.
+ ///
+ /// The bit writer receiving the segmentation parameters.
+ /// The frame header containing segmentation feature data.
+ private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
- _ = sequenceHeader;
- Guard.IsFalse(frameHeader.SegmentationParameters.Enabled, nameof(frameHeader.SegmentationParameters.Enabled), "Segmentation not supported yet.");
- writer.WriteBoolean(false);
+ ObuSegmentationParameters segmentation = frameHeader.SegmentationParameters;
+ writer.WriteBoolean(segmentation.Enabled);
+ if (!segmentation.Enabled)
+ {
+ return;
+ }
+
+ // The still-image writer emits independent intra frames with no primary reference.
+ // AV1 therefore infers update-map and update-data as enabled and carries feature data
+ // directly, without the inter-frame update flags.
+ for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++)
+ {
+ for (int featureId = 0; featureId < Av1Constants.SegmentationLevelMax; featureId++)
+ {
+ bool enabled = segmentation.FeatureEnabled[segmentId, featureId];
+ writer.WriteBoolean(enabled);
+ if (!enabled)
+ {
+ continue;
+ }
+
+ int bitCount = Av1Constants.SegmentationFeatureBits[featureId];
+ int value = segmentation.FeatureData[segmentId, featureId];
+ if (Av1Constants.SegmentationFeatureSigned[featureId] == 1)
+ {
+ writer.WriteSignedFromUnsigned(value, bitCount + 1);
+ }
+ else
+ {
+ writer.WriteLiteral((uint)value, bitCount);
+ }
+ }
+ }
}
///
- /// 5.9.11. Loop filter params syntax
+ /// Writes the deblocking-loop-filter levels and optional reference and mode deltas.
///
+ /// The bit writer receiving the loop-filter parameters.
+ /// The sequence header defining active color planes.
+ /// The frame header containing the loop-filter parameters.
private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy)
@@ -658,14 +686,29 @@ internal class ObuWriter
writer.WriteBoolean(frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate);
if (frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate)
{
- throw new NotImplementedException("Reference update of loop filter not supported yet.");
+ // An independent still frame can emit every current delta as an update. This is
+ // slightly larger than comparing against retained state but requires no video
+ // reference-frame state and produces the same observable filter parameters.
+ for (int i = 0; i < Av1Constants.TotalReferencesPerFrame; i++)
+ {
+ writer.WriteBoolean(true);
+ writer.WriteSignedFromUnsigned(frameHeader.LoopFilterParameters.ReferenceDeltas[i], 7);
+ }
+
+ for (int i = 0; i < 2; i++)
+ {
+ writer.WriteBoolean(true);
+ writer.WriteSignedFromUnsigned(frameHeader.LoopFilterParameters.ModeDeltas[i], 7);
+ }
}
}
}
///
- /// 5.9.21. TX mode syntax.
+ /// Writes the transform-size selection mode when the frame is not lossless.
///
+ /// The bit writer receiving the transform-mode flag.
+ /// The frame header containing the transform mode.
private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
if (!frameHeader.CodedLossless)
@@ -674,6 +717,12 @@ internal class ObuWriter
}
}
+ ///
+ /// Writes the loop-restoration type and restoration-unit size for each plane.
+ ///
+ /// The bit writer receiving the loop-restoration parameters.
+ /// The sequence header defining restoration availability and color planes.
+ /// The frame header containing restoration parameters.
private static void WriteLoopRestorationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration)
@@ -696,7 +745,7 @@ internal class ObuWriter
}
else
{
- writer.WriteLiteral(unitShift & 0x01, 1);
+ writer.WriteBoolean(unitShift > 0);
if (unitShift > 0)
{
writer.WriteLiteral(unitShift - 1, 1);
@@ -710,19 +759,39 @@ internal class ObuWriter
}
}
+ ///
+ /// Writes constrained directional enhancement filter strengths for the active planes.
+ ///
+ /// The bit writer receiving the CDEF parameters.
+ /// The sequence header defining CDEF availability and color planes.
+ /// The frame header containing CDEF strengths.
private static void WriteCdefParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
- _ = writer;
- _ = sequenceHeader;
-
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableCdef)
{
return;
}
- throw new NotImplementedException("Didn't implement writing CDEF yet.");
+ ObuConstraintDirectionalEnhancementFilterParameters cdef = frameHeader.CdefParameters;
+ writer.WriteLiteral((uint)cdef.Damping - 3, 2);
+ writer.WriteLiteral((uint)cdef.BitCount, 2);
+ int strengthCount = 1 << cdef.BitCount;
+ bool hasChroma = sequenceHeader.ColorConfig.PlaneCount > 1;
+ for (int i = 0; i < strengthCount; i++)
+ {
+ writer.WriteLiteral((uint)cdef.YStrength[i], 6);
+ if (hasChroma)
+ {
+ writer.WriteLiteral((uint)cdef.UvStrength[i], 6);
+ }
+ }
}
+ ///
+ /// Writes global-motion parameters when permitted by the frame type.
+ ///
+ /// The bit writer positioned at the global-motion syntax.
+ /// The current frame header.
private static void WriteGlobalMotionParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
_ = writer;
@@ -736,6 +805,11 @@ internal class ObuWriter
throw new InvalidImageContentException("AVIF files can only contain INTRA frames.");
}
+ ///
+ /// Writes reference-mode selection when permitted by the frame type.
+ ///
+ /// The bit writer positioned at the reference-mode syntax.
+ /// The current frame header.
private static void WriteFrameReferenceMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
_ = writer;
@@ -749,6 +823,11 @@ internal class ObuWriter
throw new InvalidImageContentException("AVIF files can only contain INTRA frames.");
}
+ ///
+ /// Writes the skip-mode flag when skip mode is available.
+ ///
+ /// The bit writer receiving the skip-mode flag.
+ /// The frame header containing skip-mode state.
private static void WriteSkipModeParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
if (frameHeader.SkipModeParameters.SkipModeAllowed)
@@ -757,6 +836,12 @@ internal class ObuWriter
}
}
+ ///
+ /// Writes film-grain synthesis parameters for a displayed still-image frame.
+ ///
+ /// The bit writer receiving the film-grain parameters.
+ /// The sequence header defining film-grain availability and color sampling.
+ /// The frame header containing film-grain parameters.
private static void WriteFilmGrainFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
ObuFilmGrainParameters grainParams = frameHeader.FilmGrainParameters;
@@ -802,7 +887,7 @@ internal class ObuWriter
writer.WriteLiteral(grainParams.NumCrPoints, 4);
Guard.NotNull(grainParams.PointCrValue);
Guard.NotNull(grainParams.PointCrScaling);
- for (int i = 0; i < grainParams.NumCbPoints; i++)
+ for (int i = 0; i < grainParams.NumCrPoints; i++)
{
writer.WriteLiteral(grainParams.PointCrValue[i], 8);
writer.WriteLiteral(grainParams.PointCrScaling[i], 8);
@@ -813,10 +898,10 @@ internal class ObuWriter
writer.WriteLiteral(grainParams.ArCoeffLag, 2);
uint numPosLuma = 2 * grainParams.ArCoeffLag * (grainParams.ArCoeffLag + 1);
- uint numPosChroma = 0;
+ uint numPosChroma = numPosLuma;
if (grainParams.NumYPoints != 0)
{
- numPosChroma = numPosLuma + 1;
+ numPosChroma++;
Guard.NotNull(grainParams.ArCoeffsYPlus128);
for (int i = 0; i < numPosLuma; i++)
{
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs
index 0f9773702..e5bf28aaf 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs
@@ -232,4 +232,56 @@ internal class Av1QuantizationLookup
return baseQIndex;
}
}
+
+ ///
+ /// Derives the per-segment quantizer indices, lossless flags, and quantization-matrix levels for a frame.
+ ///
+ /// The frame header whose quantization state is updated.
+ public static void UpdateFrameQuantizationState(ObuFrameHeader frameHeader)
+ {
+ ObuQuantizationParameters quantization = frameHeader.QuantizationParameters;
+ ObuSegmentationParameters segmentation = frameHeader.SegmentationParameters;
+ frameHeader.CodedLossless = true;
+ segmentation.QMLevel[0] = new int[Av1Constants.MaxSegmentCount];
+ segmentation.QMLevel[1] = new int[Av1Constants.MaxSegmentCount];
+ segmentation.QMLevel[2] = new int[Av1Constants.MaxSegmentCount];
+ for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++)
+ {
+ int qIndex = GetQIndex(segmentation, segmentId, quantization.BaseQIndex);
+ quantization.QIndex[segmentId] = qIndex;
+ frameHeader.LosslessArray[segmentId] = qIndex == 0 &&
+ quantization.DeltaQDc[(int)Av1Plane.Y] == 0 &&
+ quantization.DeltaQAc[(int)Av1Plane.U] == 0 &&
+ quantization.DeltaQDc[(int)Av1Plane.U] == 0 &&
+ quantization.DeltaQAc[(int)Av1Plane.V] == 0 &&
+ quantization.DeltaQDc[(int)Av1Plane.V] == 0;
+
+ if (!frameHeader.LosslessArray[segmentId])
+ {
+ frameHeader.CodedLossless = false;
+ }
+
+ if (quantization.IsUsingQMatrix)
+ {
+ // Lossless segments use the identity matrix level; lossy segments inherit the
+ // plane-specific levels signaled by the frame quantization parameters.
+ segmentation.QMLevel[(int)Av1Plane.Y][segmentId] = frameHeader.LosslessArray[segmentId]
+ ? 15
+ : quantization.QMatrix[(int)Av1Plane.Y];
+
+ segmentation.QMLevel[(int)Av1Plane.U][segmentId] = frameHeader.LosslessArray[segmentId]
+ ? 15
+ : quantization.QMatrix[(int)Av1Plane.U];
+
+ segmentation.QMLevel[(int)Av1Plane.V][segmentId] = frameHeader.LosslessArray[segmentId]
+ ? 15
+ : quantization.QMatrix[(int)Av1Plane.V];
+ }
+ }
+
+ frameHeader.AllLossless = frameHeader.CodedLossless &&
+ frameHeader.FrameSize.FrameWidth == frameHeader.FrameSize.SuperResolutionUpscaledWidth;
+
+ // Header syntax and tile quantization now observe the same derived frame state.
+ }
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
index c19482cbf..ef130f777 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
@@ -523,7 +523,7 @@ internal partial class Av1TileWriter
}
if (!macroBlockModeInfo.Block.UseIntraBlockCopy &&
- IsFilterIntraAllowed(scs.SequenceHeader.FilterIntraLevel > 0, blockSize, blk_ptr.PaletteSize[0], intra_luma_mode))
+ IsFilterIntraAllowed(scs.SequenceHeader.EnableFilterIntra, blockSize, blk_ptr.PaletteSize[0], intra_luma_mode))
{
writer.WriteFilterIntraMode(blk_ptr.FilterIntraMode, blockSize);
}