diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs
index 28d48b13c..54ec00ca1 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs
@@ -3,9 +3,23 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Identifies the sample precision of an AV1 sequence.
+///
internal enum Av1BitDepth : int
{
+ ///
+ /// Eight bits per sample.
+ ///
EightBit = 0,
+
+ ///
+ /// Ten bits per sample.
+ ///
TenBit = 1,
+
+ ///
+ /// Twelve bits per sample.
+ ///
TwelveBit = 2,
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs
index 615ed0b8d..06aef289a 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs
@@ -5,7 +5,15 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Provides sample-precision conversions for AV1 bit-depth values.
+///
internal static class Av1BitDepthExtensions
{
+ ///
+ /// Gets the number of bits represented by an AV1 bit-depth value.
+ ///
+ /// The AV1 bit-depth value.
+ /// Eight, ten, or twelve.
public static int GetBitCount(this Av1BitDepth bitDepth) => 8 + ((int)bitDepth << 1);
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs
index 1b96c26d9..35945069b 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs
@@ -3,23 +3,48 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Reads AV1 fixed-width and variable-length syntax from a most-significant-bit-first byte span.
+///
internal ref struct Av1BitStreamReader
{
+ ///
+ /// The complete encoded byte span.
+ ///
private readonly Span data;
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The encoded AV1 data.
public Av1BitStreamReader(Span data) => this.data = data;
+ ///
+ /// Gets the zero-based position of the next bit to read.
+ ///
public int BitPosition { get; private set; } = 0;
///
- /// Gets the number of bytes in the readers buffer.
+ /// Gets the number of bytes in the reader's buffer.
///
public readonly int Length => this.data.Length;
+ ///
+ /// Moves the next read position to the beginning of the buffer.
+ ///
public void Reset() => this.BitPosition = 0;
+ ///
+ /// Advances the read position without interpreting the skipped bits.
+ ///
+ /// The number of bits to skip.
public void Skip(int bitCount) => this.BitPosition += bitCount;
+ ///
+ /// Reads an unsigned fixed-width value in most-significant-bit-first order.
+ ///
+ /// The number of bits to read.
+ /// The decoded unsigned value.
public uint ReadLiteral(int bitCount)
{
DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount));
@@ -33,6 +58,10 @@ internal ref struct Av1BitStreamReader
return literal;
}
+ ///
+ /// Reads the next encoded bit.
+ ///
+ /// Zero or one.
internal uint ReadBit()
{
int byteOffset = Av1Math.DivideBy8Floor(this.BitPosition);
@@ -41,11 +70,19 @@ internal ref struct Av1BitStreamReader
return (uint)((this.data[byteOffset] >> shift) & 0x01);
}
+ ///
+ /// Reads the next encoded bit as a Boolean value.
+ ///
+ /// for one; otherwise, .
internal bool ReadBoolean() => this.ReadLiteral(1) > 0;
+ ///
+ /// Reads an AV1 little-endian base-128 value from a byte-aligned position.
+ ///
+ /// Receives the number of encoded bytes consumed.
+ /// The decoded unsigned value.
public ulong ReadLittleEndianBytes128(out int length)
{
- // See section 4.10.5 of the AV1-Specification
DebugGuard.IsTrue((this.BitPosition & 0x07) == 0, $"Reading of Little Endian 128 value only allowed on byte alignment (offset {this.BitPosition}).");
ulong value = 0;
@@ -64,9 +101,12 @@ internal ref struct Av1BitStreamReader
return value;
}
+ ///
+ /// Reads the AV1 unsigned-variable-length code.
+ ///
+ /// The decoded unsigned value.
public uint ReadUnsignedVariableLength()
{
- // See section 4.10.3 of the AV1-Specification
int leadingZerosCount = 0;
while (leadingZerosCount < 32)
{
@@ -94,9 +134,13 @@ internal ref struct Av1BitStreamReader
return 0;
}
+ ///
+ /// Reads a value from an alphabet whose size is not a power of two.
+ ///
+ /// The number of symbols in the alphabet.
+ /// A decoded symbol in the range zero through minus one.
public uint ReadNonSymmetric(uint n)
{
- // See section 4.10.7 of the AV1-Specification
if (n <= 1)
{
return 0;
@@ -113,15 +157,19 @@ internal ref struct Av1BitStreamReader
return (v << 1) - m + this.ReadLiteral(1);
}
+ ///
+ /// Reads a fixed-width two's-complement signed integer.
+ ///
+ /// The encoded bit width.
+ /// The sign-extended integer.
public int ReadSignedFromUnsigned(int n)
{
- // See section 4.10.6 of the AV1-Specification
int signedValue;
uint value = this.ReadLiteral(n);
uint signMask = 1U << (n - 1);
if ((value & signMask) == signMask)
{
- // Prevent overflow by casting to long;
+ // The subtraction represents sign extension; widening first preserves the n=32 case.
signedValue = (int)((long)value - (signMask << 1));
}
else
@@ -132,9 +180,13 @@ internal ref struct Av1BitStreamReader
return signedValue;
}
+ ///
+ /// Reads a byte-aligned unsigned integer whose least-significant byte is encoded first.
+ ///
+ /// The number of bytes to read.
+ /// The decoded unsigned integer.
public uint ReadLittleEndian(int n)
{
- // See section 4.10.4 of the AV1-Specification
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Reading of Little Endian value only allowed on byte alignment");
uint t = 0;
@@ -146,6 +198,11 @@ internal ref struct Av1BitStreamReader
return t;
}
+ ///
+ /// Gets a byte-aligned tile payload for entropy decoding and advances past it.
+ ///
+ /// The tile payload length in bytes.
+ /// The tile payload span.
public Span GetSymbolReader(int tileDataSize)
{
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Symbol reading needs to start on byte boundary.");
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
index 687b97333..3c31d21a8 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
@@ -5,14 +5,40 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Writes AV1 fixed-width and variable-length syntax to reusable expanding memory.
+///
internal ref struct Av1BitStreamWriter
{
+ ///
+ /// The number of bits in one output byte.
+ ///
private const int WordSize = 8;
+
+ ///
+ /// The expanding output allocation.
+ ///
private readonly AutoExpandingMemory memory;
+
+ ///
+ /// The current writable view over .
+ ///
private Span span;
+
+ ///
+ /// The final byte index that can be written without expanding .
+ ///
private int capacityTrigger;
+
+ ///
+ /// The partially assembled output byte.
+ ///
private byte buffer = 0;
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The reusable expanding output allocation.
public Av1BitStreamWriter(AutoExpandingMemory memory)
{
this.memory = memory;
@@ -20,36 +46,45 @@ internal ref struct Av1BitStreamWriter
this.capacityTrigger = memory.Capacity - 1;
}
+ ///
+ /// Gets the zero-based position of the next output bit.
+ ///
public int BitPosition { get; private set; } = 0;
+ ///
+ /// Gets the current output capacity in bytes.
+ ///
public readonly int Capacity => this.memory.Capacity;
+ ///
+ /// Encodes an unsigned 32-bit value using little-endian base-128 bytes.
+ ///
+ /// The value to encode.
+ /// The destination receiving up to five bytes.
+ /// The number of bytes written.
public static int GetLittleEndianBytes128(uint value, Span span)
{
- if (value < 0x80U)
- {
- span[0] = (byte)value;
- return 1;
- }
- else if (value < 0x8000U)
- {
- span[0] = (byte)((value & 0x7fU) | 0x80U);
- span[1] = (byte)((value >> 7) & 0xff);
- return 2;
- }
- else if (value < 0x800000U)
+ int length = 0;
+ do
{
- span[0] = (byte)((value & 0x7fU) | 0x80U);
- span[1] = (byte)((value >> 7) & 0xff);
- span[2] = (byte)((value >> 14) & 0xff);
- return 3;
- }
- else
- {
- throw new NotImplementedException("No such large values yet.");
+ byte encodedByte = (byte)(value & 0x7fU);
+ value >>= 7;
+ if (value != 0)
+ {
+ encodedByte |= 0x80;
+ }
+
+ span[length++] = encodedByte;
}
+ while (value != 0);
+
+ return length;
}
+ ///
+ /// Advances the output position, emitting the current byte whenever the skip crosses a byte boundary.
+ ///
+ /// The number of bits to skip.
public void Skip(int bitCount)
{
this.BitPosition += bitCount;
@@ -60,6 +95,9 @@ internal ref struct Av1BitStreamWriter
}
}
+ ///
+ /// Writes a partially assembled byte and resets the position for output-memory reuse.
+ ///
public void Flush()
{
if (Av1Math.Modulus8(this.BitPosition) != 0)
@@ -71,6 +109,11 @@ internal ref struct Av1BitStreamWriter
this.BitPosition = 0;
}
+ ///
+ /// Writes an unsigned fixed-width value in most-significant-bit-first order.
+ ///
+ /// The value to write.
+ /// The number of low-order bits to write.
public void WriteLiteral(uint value, int bitCount)
{
for (int bit = bitCount - 1; bit >= 0; bit--)
@@ -79,15 +122,23 @@ internal ref struct Av1BitStreamWriter
}
}
+ ///
+ /// Writes one Boolean bit.
+ ///
+ /// The Boolean value.
internal void WriteBoolean(bool value)
{
byte boolByte = value ? (byte)1 : (byte)0;
this.WriteBit(boolByte);
}
+ ///
+ /// Writes a fixed-width signed integer in two's-complement form.
+ ///
+ /// The signed value.
+ /// The encoded bit width.
public void WriteSignedFromUnsigned(int signedValue, int n)
{
- // See section 4.10.6 of the AV1-Specification
ulong value = (ulong)signedValue;
if (signedValue < 0)
{
@@ -97,15 +148,32 @@ internal ref struct Av1BitStreamWriter
this.WriteLiteral((uint)value, n);
}
+ ///
+ /// Writes an unsigned 32-bit value using little-endian base-128 bytes.
+ ///
+ /// The value to write.
public void WriteLittleEndianBytes128(uint value)
{
- int bytesWritten = GetLittleEndianBytes128(value, this.span.Slice(this.BitPosition >> 3));
+ int wordPosition = this.BitPosition >> 3;
+ const int maximumEncodedLength = 5;
+ if (this.span.Length - wordPosition < maximumEncodedLength)
+ {
+ this.memory.GetSpan(wordPosition + maximumEncodedLength);
+ this.span = this.memory.GetEntireSpan();
+ this.capacityTrigger = this.span.Length - 1;
+ }
+
+ int bytesWritten = GetLittleEndianBytes128(value, this.span[wordPosition..]);
this.BitPosition += bytesWritten << 3;
}
+ ///
+ /// Writes a value from an alphabet whose size is not a power of two.
+ ///
+ /// The symbol value.
+ /// The number of symbols in the alphabet.
internal void WriteNonSymmetric(uint value, uint numberOfSymbols)
{
- // See section 4.10.7 of the AV1-Specification
if (numberOfSymbols <= 1)
{
return;
@@ -126,6 +194,10 @@ internal ref struct Av1BitStreamWriter
}
}
+ ///
+ /// Appends one bit to the partially assembled output byte.
+ ///
+ /// Zero or one.
private void WriteBit(byte value)
{
int bit = this.BitPosition & 0x07;
@@ -138,9 +210,13 @@ internal ref struct Av1BitStreamWriter
this.BitPosition++;
}
+ ///
+ /// Writes an unsigned integer with its least-significant byte first.
+ ///
+ /// The value to write.
+ /// The number of bytes to write.
public void WriteLittleEndian(uint value, int n)
{
- // See section 4.10.4 of the AV1-Specification
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Little Endian value only allowed on byte alignment");
uint t = value;
@@ -151,6 +227,10 @@ internal ref struct Av1BitStreamWriter
}
}
+ ///
+ /// Writes a byte-aligned entropy-coded tile payload.
+ ///
+ /// The tile payload.
internal void WriteBlob(Span tileData)
{
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Tile Data only allowed on byte alignment");
@@ -166,6 +246,9 @@ internal ref struct Av1BitStreamWriter
this.BitPosition += tileData.Length << 3;
}
+ ///
+ /// Stores the current output byte, expanding the allocation when necessary.
+ ///
private void WriteBuffer()
{
int wordPosition = Av1Math.DivideBy8Floor(this.BitPosition);
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs
index 62c4ff59e..e4646508d 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs
@@ -3,10 +3,11 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Identifies every luma block size defined by AV1.
+///
internal enum Av1BlockSize : byte
{
- // See sction 6.10.4 of the Av1 Specification.
-
/// A block of samples, 4 samples wide and 4 samples high.
Block4x4 = 0,
@@ -72,8 +73,24 @@ internal enum Av1BlockSize : byte
/// A block of samples, 64 samples wide and 16 samples high.
Block64x16 = 21,
+
+ ///
+ /// The number of concrete block-size values.
+ ///
AllSizes = 22,
+
+ ///
+ /// The first extended rectangular block size following the primary size set.
+ ///
SizeS = Block4x16,
+
+ ///
+ /// A sentinel representing an invalid block size.
+ ///
Invalid = 255,
+
+ ///
+ /// The final value in the primary block-size set.
+ ///
Largest = SizeS - 1,
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
index df5f53380..eafde6bef 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
@@ -5,12 +5,24 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Provides dimensions, chroma subsampling, and transform limits for AV1 block sizes.
+///
internal static class Av1BlockSizeExtensions
{
+ ///
+ /// The width of each block size in units of four samples.
+ ///
private static readonly int[] SizeWide = [1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16];
+
+ ///
+ /// The height of each block size in units of four samples.
+ ///
private static readonly int[] SizeHigh = [1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4];
- // The Subsampled_Size table in the spec (Section 5.11.38. Get plane residual size function).
+ ///
+ /// Maps each luma block size and pair of chroma subsampling shifts to its residual-plane block size.
+ ///
private static readonly Av1BlockSize[][][] SubSampled =
[
@@ -40,6 +52,9 @@ internal static class Av1BlockSizeExtensions
[[Av1BlockSize.Block64x16, Av1BlockSize.Invalid], [Av1BlockSize.Block32x16, Av1BlockSize.Block32x8]]
];
+ ///
+ /// Maps each block size to its largest permitted transform size.
+ ///
private static readonly Av1TransformSize[] MaxTransformSize = [
Av1TransformSize.Size4x4, Av1TransformSize.Size4x8, Av1TransformSize.Size8x4, Av1TransformSize.Size8x8,
Av1TransformSize.Size8x16, Av1TransformSize.Size16x8, Av1TransformSize.Size16x16, Av1TransformSize.Size16x32,
@@ -49,9 +64,15 @@ internal static class Av1BlockSizeExtensions
Av1TransformSize.Size16x64, Av1TransformSize.Size64x16
];
+ ///
+ /// Contains the base-two logarithm of the sample count for each block size.
+ ///
private static readonly int[] PelsLog2Count =
[4, 5, 5, 6, 7, 7, 8, 9, 9, 10, 11, 11, 12, 13, 13, 14, 6, 6, 8, 8, 10, 10];
+ ///
+ /// Maps geometry dimension logarithms to an AV1 block size using the mode-decision scan's transposed axis convention.
+ ///
private static readonly Av1BlockSize[][] HeightWidthToSize = [
[Av1BlockSize.Block4x4, Av1BlockSize.Block4x8, Av1BlockSize.Block4x16, Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid],
[Av1BlockSize.Block8x4, Av1BlockSize.Block8x8, Av1BlockSize.Block8x16, Av1BlockSize.Block8x32, Av1BlockSize.Invalid, Av1BlockSize.Invalid],
@@ -61,51 +82,81 @@ internal static class Av1BlockSizeExtensions
[Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64, Av1BlockSize.Block128x128]
];
+ ///
+ /// Gets the block width in units of four samples.
+ ///
+ /// The block size.
+ /// The number of four-sample columns.
public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize];
+ ///
+ /// Gets the block height in units of four samples.
+ ///
+ /// The block size.
+ /// The number of four-sample rows.
public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize];
///
- /// Gets the given by the Log2 of the width and height.
+ /// Gets the block size from mode-decision geometry dimension logarithms, where zero represents four samples.
///
- /// Log2 of the width value.
- /// Log2 of the height value.
- /// The .
- public static Av1BlockSize FromWidthAndHeight(uint widthLog2, uint heightLog2) => HeightWidthToSize[heightLog2][widthLog2];
+ /// The base-two width logarithm minus two.
+ /// The base-two height logarithm minus two.
+ /// The matching block size, or for unsupported dimensions.
+ public static Av1BlockSize FromWidthAndHeight(uint widthLog2, uint heightLog2)
+ {
+ // Mode-decision geometry is ported with its source axis order, so its size lookup is indexed height first.
+ return HeightWidthToSize[heightLog2][widthLog2];
+ }
///
- /// Returns the width of the block in samples.
+ /// Gets the block width in samples.
///
+ /// The block size.
+ /// The block width in samples.
public static int GetWidth(this Av1BlockSize blockSize)
=> Get4x4WideCount(blockSize) << 2;
///
- /// Returns of the height of the block in 4 samples.
+ /// Gets the block height in samples.
///
+ /// The block size.
+ /// The block height in samples.
public static int GetHeight(this Av1BlockSize blockSize)
=> Get4x4HighCount(blockSize) << 2;
///
- /// Returns base 2 logarithm of the width of the block in units of 4 samples.
+ /// Gets the base-two logarithm of the block width in units of four samples.
///
+ /// The block size.
+ /// The base-two logarithm of the four-sample column count.
public static int Get4x4WidthLog2(this Av1BlockSize blockSize)
=> Av1Math.Log2(Get4x4WideCount(blockSize));
///
- /// Returns base 2 logarithm of the height of the block in units of 4 samples.
+ /// Gets the base-two logarithm of the block height in units of four samples.
///
+ /// The block size.
+ /// The base-two logarithm of the four-sample row count.
public static int Get4x4HeightLog2(this Av1BlockSize blockSize)
=> Av1Math.Log2(Get4x4HighCount(blockSize));
///
- /// Returns the block size of a sub sampled block.
+ /// Gets the residual-plane block size for Boolean chroma subsampling flags.
///
+ /// The luma block size.
+ /// Indicates horizontal chroma subsampling.
+ /// Indicates vertical chroma subsampling.
+ /// The corresponding residual-plane block size.
public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, bool subX, bool subY)
=> GetSubsampled(blockSize, subX ? 1 : 0, subY ? 1 : 0);
///
- /// Returns the block size of a sub sampled block.
+ /// Gets the residual-plane block size for chroma subsampling shifts.
///
+ /// The luma block size.
+ /// The horizontal chroma subsampling shift.
+ /// The vertical chroma subsampling shift.
+ /// The corresponding residual-plane block size, or when unavailable.
public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, int subX, int subY)
{
if (blockSize == Av1BlockSize.Invalid)
@@ -116,6 +167,13 @@ internal static class Av1BlockSizeExtensions
return SubSampled[(int)blockSize][subX][subY];
}
+ ///
+ /// Gets the maximum chroma transform size after applying plane subsampling and AV1 chroma transform limits.
+ ///
+ /// The luma block size.
+ /// Indicates horizontal chroma subsampling.
+ /// Indicates vertical chroma subsampling.
+ /// The maximum chroma transform size, or when the plane block size is invalid.
public static Av1TransformSize GetMaxUvTransformSize(this Av1BlockSize blockSize, bool subX, bool subY)
{
Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY);
@@ -135,12 +193,18 @@ internal static class Av1BlockSizeExtensions
}
///
- /// Returns the largest transform size that can be used for blocks of given size.
- /// The can be either a square or rectangular block.
+ /// Gets the largest square or rectangular transform size permitted for a block.
///
+ /// The block size.
+ /// The maximum transform size.
public static Av1TransformSize GetMaximumTransformSize(this Av1BlockSize blockSize)
=> MaxTransformSize[(int)blockSize];
+ ///
+ /// Gets the base-two logarithm of the block's sample count.
+ ///
+ /// The block size.
+ /// The base-two logarithm of width multiplied by height.
public static int GetPelsLog2Count(this Av1BlockSize blockSize)
=> PelsLog2Count[(int)blockSize];
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs
index e0cef4775..3928ad67a 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs
@@ -3,11 +3,14 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
///
-/// Implementation of section 2.3.3 of AV1 Codec ISO Media File Format Binding specification v1.2.0.
-/// See https://aomediacodec.github.io/av1-isobmff/v1.2.0.html#av1codecconfigurationbox-syntax.
+/// Represents the decoder configuration fields stored in an AV1 codec-configuration property.
///
internal struct Av1CodecConfiguration
{
+ ///
+ /// Initializes a new instance of the struct from an AV1 codec-configuration payload.
+ ///
+ /// The configuration payload beginning with the marker and version fields.
public Av1CodecConfiguration(Span boxBuffer)
{
Av1BitStreamReader reader = new(boxBuffer);
@@ -35,29 +38,68 @@ internal struct Av1CodecConfiguration
}
}
+ ///
+ /// Gets the one-bit configuration marker.
+ ///
public byte Marker { get; }
+ ///
+ /// Gets the codec-configuration record version.
+ ///
public byte Version { get; }
+ ///
+ /// Gets the sequence profile declared by the configuration record.
+ ///
public byte SeqProfile { get; }
+ ///
+ /// Gets the first operating point's sequence level index.
+ ///
public byte SeqLevelIdx0 { get; }
+ ///
+ /// Gets the first operating point's sequence tier flag.
+ ///
public byte SeqTier0 { get; }
+ ///
+ /// Gets the high-bit-depth flag.
+ ///
public byte HighBitdepth { get; }
+ ///
+ /// Gets a value indicating whether the sequence uses twelve-bit samples.
+ ///
public bool TwelveBit { get; }
+ ///
+ /// Gets a value indicating whether the sequence contains only a luma plane.
+ ///
public bool MonoChrome { get; }
+ ///
+ /// Gets a value indicating whether chroma is horizontally subsampled.
+ ///
public bool ChromaSubsamplingX { get; }
+ ///
+ /// Gets a value indicating whether chroma is vertically subsampled.
+ ///
public bool ChromaSubsamplingY { get; }
+ ///
+ /// Gets the chroma sample-position code.
+ ///
public byte ChromaSamplePosition { get; }
+ ///
+ /// Gets a value indicating whether an initial presentation delay is declared.
+ ///
public bool InitialPresentationDelayPresent { get; }
+ ///
+ /// Gets the initial presentation delay in decoded frames, or zero when no delay is declared.
+ ///
public byte InitialPresentationDelay { get; }
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs b/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs
index 07be6a044..1ebef2924 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs
@@ -3,10 +3,28 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Identifies the AV1 luma and chroma plane sampling layout.
+///
internal enum Av1ColorFormat
{
+ ///
+ /// Monochrome luma samples without chroma planes.
+ ///
Yuv400,
+
+ ///
+ /// Chroma samples subsampled by two horizontally and vertically.
+ ///
Yuv420,
+
+ ///
+ /// Chroma samples subsampled by two horizontally.
+ ///
Yuv422,
+
+ ///
+ /// Full-resolution luma and chroma samples.
+ ///
Yuv444,
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
index 125f029d0..8b2e01b6e 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
@@ -6,195 +6,278 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Defines shared AV1 syntax, geometry, entropy, and transform limits.
+///
internal static class Av1Constants
{
+ ///
+ /// The highest sequence profile defined by AV1.
+ ///
public const ObuSequenceProfile MaxSequenceProfile = ObuSequenceProfile.Professional;
+ ///
+ /// The number of bits used for an operating-point level index.
+ ///
public const int LevelBits = 5;
///
- /// Number of fractional bits for computing position in upscaling.
+ /// The number of bits used to signal a super-resolution denominator offset.
///
- public const int SuperResolutionScaleBits = 14;
+ public const int SuperResolutionScaleBits = 3;
- public const int ScaleNumerator = -1;
+ ///
+ /// The fixed numerator of the AV1 super-resolution scaling ratio.
+ ///
+ public const int ScaleNumerator = 8;
///
- /// Number of reference frames that can be used for inter prediction.
+ /// The number of reference frames that can be used for inter prediction.
///
public const int ReferencesPerFrame = 7;
///
- /// Maximum area of a tile in units of luma samples.
+ /// The maximum area of a tile in units of luma samples.
///
public const int MaxTileArea = 4096 * 2304;
///
- /// Maximum width of a tile in units of luma samples.
+ /// The maximum width of a tile in units of luma samples.
///
public const int MaxTileWidth = 4096;
///
- /// Maximum number of tile columns.
+ /// The maximum number of tile columns.
///
public const int MaxTileColumnCount = 64;
///
- /// Maximum number of tile rows.
+ /// The maximum number of tile rows.
///
public const int MaxTileRowCount = 64;
///
- /// Number of frames that can be stored for future reference.
+ /// The number of frames that can be stored for future reference.
///
public const int ReferenceFrameCount = 8;
///
- /// Value of 'PrimaryReferenceFrame' indicating that there is no primary reference frame.
+ /// The primary-reference-frame value indicating that no primary reference is selected.
///
public const uint PrimaryReferenceFrameNone = 7;
- public const int PimaryReferenceBits = 3;
+ ///
+ /// The number of bits used to signal a primary reference frame.
+ ///
+ public const int PrimaryReferenceBits = 3;
///
- /// Number of segments allowed in segmentation map.
+ /// The number of segments allowed in a segmentation map.
///
public const int MaxSegmentCount = 8;
///
- /// Smallest denominator for upscaling ratio.
+ /// The smallest signaled denominator for an active super-resolution ratio.
///
public const int SuperResolutionScaleDenominatorMinimum = 9;
///
- /// Base 2 logarithm of maximum size of a superblock in luma samples.
+ /// The base-two logarithm of the maximum superblock size in luma samples.
///
public const int MaxSuperBlockSizeLog2 = 7;
///
- /// Base 2 logarithm of smallest size of a mode info block.
+ /// The base-two logarithm of the smallest mode-info block size in luma samples.
///
public const int ModeInfoSizeLog2 = 2;
+ ///
+ /// The maximum quantizer index.
+ ///
public const int MaxQ = 255;
///
- /// Number of segmentation features.
+ /// The number of segmentation features.
///
public const int SegmentationLevelMax = 8;
///
- /// Maximum size of a loop restoration tile.
+ /// The maximum loop-restoration tile size in samples.
///
public const int RestorationMaxTileSize = 256;
///
- /// Number of Wiener coefficients to read.
+ /// The number of independent Wiener filter coefficients per direction.
///
public const int WienerCoefficientCount = 3;
+ ///
+ /// The number of luma and chroma frame loop-filter levels.
+ ///
public const int FrameLoopFilterCount = 4;
///
- /// Value indicating alternative encoding of quantizer index delta values.
+ /// The first quantizer-delta magnitude encoded through the escape path.
///
public const int DeltaQuantizerSmall = 3;
///
- /// Value indicating alternative encoding of loop filter delta values.
+ /// The first loop-filter-delta magnitude encoded through the escape path.
///
public const int DeltaLoopFilterSmall = 3;
///
- /// Maximum value used for loop filtering.
+ /// The maximum loop-filter strength.
///
public const int MaxLoopFilter = 63;
///
- /// Maximum magnitude of AngleDeltaY and AngleDeltaUV.
+ /// The maximum directional-prediction angle-delta magnitude.
///
public const int MaxAngleDelta = 3;
///
- /// Maximum number of color planes.
+ /// The maximum number of color planes.
///
public const int MaxPlanes = 3;
///
- /// Number of reference frame types (including intra type).
+ /// The number of reference-frame types, including the intra type.
///
public const int TotalReferencesPerFrame = 8;
///
- /// Number of values for palette_size.
+ /// The maximum palette size.
///
public const int PaletteMaxSize = 8;
///
- /// Maximum transform size categories.
+ /// The number of transform-size probability categories.
///
public const int MaxTransformCategories = 4;
+ ///
+ /// The number of cumulative coefficient-level magnitude contexts.
+ ///
public const int CoefficientContextCount = 6;
+ ///
+ /// The number of coefficient magnitudes represented by base symbols before base-range coding.
+ ///
public const int BaseLevelsCount = 2;
+ ///
+ /// The maximum coefficient magnitude increment represented by base-range symbols.
+ ///
public const int CoefficientBaseRange = 12;
+ ///
+ /// The maximum transform dimension in samples.
+ ///
public const int MaxTransformSize = 1 << 6;
+ ///
+ /// The maximum transform dimension in units of four samples.
+ ///
public const int MaxTransformSizeUnit = MaxTransformSize >> 2;
+ ///
+ /// The number of low-order bits reserved for a cumulative coefficient-level context.
+ ///
public const int CoefficientContextBitCount = 6;
+ ///
+ /// The mask selecting the cumulative coefficient-level magnitude bits.
+ ///
public const int CoefficientContextMask = (1 << CoefficientContextBitCount) - 1;
+ ///
+ /// The base-two logarithm of the horizontal coefficient-context padding.
+ ///
public const int TransformPadHorizontalLog2 = 2;
+ ///
+ /// The horizontal coefficient-context padding in elements.
+ ///
public const int TransformPadHorizontal = 1 << TransformPadHorizontalLog2;
+ ///
+ /// The total vertical coefficient-context padding in rows.
+ ///
public const int TransformPadVertical = 6;
+ ///
+ /// The trailing coefficient-context padding in elements.
+ ///
public const int TransformPadEnd = 16;
+ ///
+ /// The maximum padded two-dimensional coefficient-context allocation size.
+ ///
public const int TransformPad2d = ((MaxTransformSize + TransformPadHorizontal) * (MaxTransformSize + TransformPadVertical)) + TransformPadEnd;
+ ///
+ /// The coefficient-context padding above a transform.
+ ///
public const int TransformPadTop = 2;
+ ///
+ /// The coefficient-context padding below a transform.
+ ///
public const int TransformPadBottom = 4;
+ ///
+ /// The largest symbol in a coefficient base-range distribution.
+ ///
public const int BaseRangeSizeMinus1 = 3;
+ ///
+ /// The largest coefficient magnitude represented before Golomb coding.
+ ///
public const int MaxBaseRange = 15;
///
- /// Log2 of number of values for ChromaFromLuma Alpha U and ChromaFromLuma Alpha V.
+ /// The base-two logarithm of the chroma-from-luma alpha alphabet size.
///
public const int ChromaFromLumaAlphabetSizeLog2 = 4;
///
- /// Total number of Quantification Matrices sets stored.
+ /// The number of quantization-matrix levels.
///
public const int QuantificationMatrixLevelCount = 1 << 4;
+ ///
+ /// The fixed-point precision of each quantization-matrix element.
+ ///
public const int QuantizationMatrixElementBitCount = 5;
+ ///
+ /// The directional intra-prediction angle increment in degrees.
+ ///
public const int AngleStep = 3;
///
- /// Maximum number of stages in a 1-dimensioanl transform function.
+ /// The maximum number of stages in a one-dimensional transform function.
///
public const int MaxTransformStageNumber = 12;
+ ///
+ /// The number of partition contexts per block-size logarithm.
+ ///
public const int PartitionProbabilitySet = 4;
- // Number of transform sizes that use extended transforms.
+ ///
+ /// The number of square transform-size contexts that can signal extended transforms.
+ ///
public const int ExtendedTransformCount = 4;
+ ///
+ /// The highest variable-transform depth index.
+ ///
public const int MaxVarTransform = 2;
///
- /// Maximum number of transform blocks per depth
+ /// The maximum number of transform blocks at one depth.
///
public const int MaxTransformBlockCount = 16;
@@ -203,5 +286,8 @@ internal static class Av1Constants
///
public const int PlaneTypeCount = 2;
+ ///
+ /// The maximum number of transform units stored for one encoded block.
+ ///
public const int MaxTransformUnitCount = 16;
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
index 66586d0c1..74c8b8da2 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
@@ -9,24 +9,57 @@ using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Decodes one AV1 still-image elementary stream into an ImageSharp image.
+///
internal class Av1Decoder : IAv1TileReader
{
+ ///
+ /// The open-bitstream-unit parser for the current image item.
+ ///
private readonly ObuReader obuReader;
+
+ ///
+ /// The configuration used for decoded image and scratch-memory allocation.
+ ///
private readonly Configuration configuration;
+
+ ///
+ /// The tile parser shared by all tile groups in the current frame.
+ ///
private Av1TileReader? tileReader;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration used for image and scratch-memory allocation.
public Av1Decoder(Configuration configuration)
{
this.configuration = configuration;
this.obuReader = new();
}
+ ///
+ /// Gets the decoded frame header, or before the stream provides one.
+ ///
public ObuFrameHeader? FrameHeader { get; private set; }
+ ///
+ /// Gets the decoded sequence header, or before the stream provides one.
+ ///
public ObuSequenceHeader? SequenceHeader { get; private set; }
+ ///
+ /// Gets the tile and superblock state for the decoded frame, or before tile parsing completes.
+ ///
public Av1FrameInfo? FrameInfo { get; private set; }
+ ///
+ /// Decodes an AV1 still-image elementary stream.
+ ///
+ /// The destination pixel type.
+ /// The complete AV1 elementary-stream payload.
+ /// The decoded image.
public Image Decode(Span buffer)
where TPixel : unmanaged, IPixel
{
@@ -66,14 +99,21 @@ internal class Av1Decoder : IAv1TileReader
}
}
+ ///
+ /// Parses one entropy-coded tile payload into the current frame state.
+ ///
+ /// The entropy-coded tile payload.
+ /// The raster-order tile index.
public void ReadTile(Span tileData, int tileNum)
{
- if (this.tileReader == null)
+ if (this.tileReader is null)
{
this.SequenceHeader = this.obuReader.SequenceHeader;
this.FrameHeader = this.obuReader.FrameHeader;
Guard.NotNull(this.SequenceHeader, nameof(this.SequenceHeader));
Guard.NotNull(this.FrameHeader, nameof(this.FrameHeader));
+
+ // Every tile group in a frame contributes to the same mode-info and coefficient state.
this.tileReader = new Av1TileReader(this.configuration, this.SequenceHeader, this.FrameHeader);
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs
index a0baba361..cc00a902e 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs
@@ -9,19 +9,54 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
///
-/// Buffer for the pixels of a single frame.
+/// Owns the padded luma and chroma sample planes for one decoded AV1 frame.
///
+/// The unmanaged storage-element type used by the plane allocations.
internal class Av1FrameBuffer : IDisposable
where T : unmanaged
{
+ ///
+ /// The number of border samples reserved for intra prediction and in-loop filtering.
+ ///
private const int DecoderPaddingValue = 72;
+
+ ///
+ /// The allocation-mask bit for the luma plane.
+ ///
private const int PictureBufferYFlag = 1 << 0;
+
+ ///
+ /// The allocation-mask bit for the first chroma plane.
+ ///
private const int PictureBufferCbFlag = 1 << 1;
+
+ ///
+ /// The allocation-mask bit for the second chroma plane.
+ ///
private const int PictureBufferCrFlag = 1 << 2;
+
+ ///
+ /// The allocation mask for a monochrome frame.
+ ///
private const int PictureBufferLumaMask = PictureBufferYFlag;
+
+ ///
+ /// The allocation mask for a frame containing all three planes.
+ ///
private const int PictureBufferFullMask = PictureBufferYFlag | PictureBufferCbFlag | PictureBufferCrFlag;
+
+ ///
+ /// The number of elements occupied by one logical sample.
+ ///
private readonly int storageElementsPerSample;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration providing the plane allocator.
+ /// The sequence header defining maximum dimensions, bit depth, and chroma layout.
+ /// The maximum color format to allocate for a non-monochrome sequence.
+ /// Indicates whether reconstruction uses native 16-bit sample storage.
public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHeader, Av1ColorFormat maxColorFormat, bool is16BitPipeline)
{
Av1ColorFormat colorFormat = sequenceHeader.ColorConfig.IsMonochrome ? Av1ColorFormat.Yuv400 : maxColorFormat;
@@ -36,7 +71,7 @@ internal class Av1FrameBuffer : IDisposable
this.ColorFormat = colorFormat;
this.Is16BitPipeline = is16BitPipeline;
- this.BufferEnableMask = sequenceHeader.ColorConfig.IsMonochrome ? PictureBufferLumaMask : PictureBufferFullMask;
+ int bufferEnableMask = sequenceHeader.ColorConfig.IsMonochrome ? PictureBufferLumaMask : PictureBufferFullMask;
int leftPadding = DecoderPaddingValue;
int rightPadding = DecoderPaddingValue;
@@ -51,7 +86,6 @@ internal class Av1FrameBuffer : IDisposable
int heightY = this.MaxHeight + topPadding + bottomPadding;
this.OriginX = leftPadding;
this.OriginY = topPadding;
- this.OriginOriginY = bottomPadding;
int strideChroma = 0;
int heightChroma = 0;
switch (this.ColorFormat)
@@ -70,34 +104,28 @@ internal class Av1FrameBuffer : IDisposable
break;
}
- this.PackedFlag = false;
-
this.BufferY = null;
this.BufferCb = null;
this.BufferCr = null;
- if ((this.BufferEnableMask & PictureBufferYFlag) != 0)
+ if ((bufferEnableMask & PictureBufferYFlag) != 0)
{
this.BufferY = configuration.MemoryAllocator.Allocate2D(strideY * this.storageElementsPerSample, heightY);
}
- if ((this.BufferEnableMask & PictureBufferCbFlag) != 0)
+ if ((bufferEnableMask & PictureBufferCbFlag) != 0)
{
this.BufferCb = configuration.MemoryAllocator.Allocate2D(strideChroma * this.storageElementsPerSample, heightChroma);
}
- if ((this.BufferEnableMask & PictureBufferCrFlag) != 0)
+ if ((bufferEnableMask & PictureBufferCrFlag) != 0)
{
this.BufferCr = configuration.MemoryAllocator.Allocate2D(strideChroma * this.storageElementsPerSample, heightChroma);
}
-
- this.BitIncrementY = null;
- this.BitIncrementCb = null;
- this.BitIncrementCr = null;
- this.BitIncrementY = null;
- this.BitIncrementCb = null;
- this.BitIncrementCr = null;
}
+ ///
+ /// Gets the padded luma-coordinate origin of the visible frame.
+ ///
public Point StartPosition { get; private set; }
///
@@ -115,12 +143,6 @@ internal class Av1FrameBuffer : IDisposable
///
public Buffer2D? BufferCr { get; private set; }
- public Buffer2D? BitIncrementY { get; private set; }
-
- public Buffer2D? BitIncrementCb { get; private set; }
-
- public Buffer2D? BitIncrementCr { get; private set; }
-
///
/// Gets or sets the horizontal padding distance.
///
@@ -132,22 +154,17 @@ internal class Av1FrameBuffer : IDisposable
public int OriginY { get; set; }
///
- /// Gets or sets the vertical bottom padding distance
- ///
- public int OriginOriginY { get; set; }
-
- ///
- /// Gets or sets the Luma picture width, which excludes the padding.
+ /// Gets or sets the luma picture width, excluding padding.
///
public int Width { get; set; }
///
- /// Gets or sets the Luma picture height, which excludes the padding.
+ /// Gets or sets the luma picture height, excluding padding.
///
public int Height { get; set; }
///
- /// Gets or sets the Lume picture width.
+ /// Gets or sets the maximum luma picture width.
///
public int MaxWidth { get; set; }
@@ -167,33 +184,23 @@ internal class Av1FrameBuffer : IDisposable
public ObuColorConfig ColorConfig { get; }
///
- /// Gets or sets the chroma subsampling.
+ /// Gets or sets the luma and chroma plane sampling layout.
///
public Av1ColorFormat ColorFormat { get; set; }
///
- /// Gets or sets the Luma picture height.
+ /// Gets or sets the maximum luma picture height.
///
public int MaxHeight { get; set; }
- public int LumaSize { get; }
-
- public int ChromaSize { get; }
-
///
- /// Gets or sets a value indicating whether the bytes of the buffers are packed.
+ /// Gets a value indicating whether reconstruction uses native 16-bit samples.
///
- public bool PackedFlag { get; set; }
+ public bool Is16BitPipeline { get; }
///
- /// Gets or sets a value indicating whether film grain parameters are present for this frame.
+ /// Releases the owned luma and chroma plane allocations.
///
- public bool FilmGrainFlag { get; set; }
-
- public int BufferEnableMask { get; set; }
-
- public bool Is16BitPipeline { get; set; }
-
public void Dispose()
{
this.BufferY?.Dispose();
@@ -202,20 +209,17 @@ internal class Av1FrameBuffer : IDisposable
this.BufferCb = null;
this.BufferCr?.Dispose();
this.BufferCr = null;
- this.BitIncrementY?.Dispose();
- this.BitIncrementY = null;
- this.BitIncrementCb?.Dispose();
- this.BitIncrementCb = null;
- this.BitIncrementCr?.Dispose();
- this.BitIncrementCr = null;
}
///
- /// Returns a starting at 1 row before this blocks pixels.
+ /// Gets a storage-element span beginning one logical row before a block.
///
- ///
- /// SVT: svt_aom_derive_blk_pointers
- ///
+ /// The luma or chroma plane.
+ /// The block origin in plane samples.
+ /// The horizontal chroma subsampling shift.
+ /// The vertical chroma subsampling shift.
+ /// Receives the logical samples between adjacent rows.
+ /// The span beginning one logical row before the block.
public Span DeriveBlockPointer(Av1Plane plane, Point locationInPixels, int subX, int subY, out int stride)
{
this.GetPlaneLayout(
@@ -233,7 +237,7 @@ internal class Av1FrameBuffer : IDisposable
int blockOffset = (((originY + locationInPixels.Y) * stride) + originX + locationInPixels.X) *
this.storageElementsPerSample;
- // Deviation from SVT, return PREVIOUS row in Block Reconstruction Buffer.
+ // Intra prediction addresses above neighbors relative to the destination span, so index zero is the previous row.
blockOffset -= elementStride;
Guard.MustBeGreaterThanOrEqualTo(blockOffset, 0, nameof(blockOffset));
@@ -241,11 +245,14 @@ internal class Av1FrameBuffer : IDisposable
}
///
- /// Returns a 16-bit sample span starting one row before the specified block.
+ /// Gets a native 16-bit sample span beginning one logical row before a block.
///
- ///
- /// SVT: svt_aom_derive_blk_pointers
- ///
+ /// The luma or chroma plane.
+ /// The block origin in plane samples.
+ /// The horizontal chroma subsampling shift.
+ /// The vertical chroma subsampling shift.
+ /// Receives the logical samples between adjacent rows.
+ /// The 16-bit span beginning one logical row before the block.
public Span DeriveBlockPointer16(Av1Plane plane, Point locationInPixels, int subX, int subY, out int stride)
{
this.GetPlaneLayout(
@@ -267,11 +274,12 @@ internal class Av1FrameBuffer : IDisposable
}
///
- /// Returns a starting at top left pixel of the block of the specified plane.
+ /// Gets the visible sample region for one plane.
///
- ///
- /// SVT: svt_aom_derive_blk_pointers
- ///
+ /// The luma or chroma plane.
+ /// The horizontal chroma subsampling shift.
+ /// The vertical chroma subsampling shift.
+ /// The plane region excluding decoder padding.
public Buffer2DRegion DeriveBlockPointer(Av1Plane plane, int subX, int subY)
{
this.GetPlaneLayout(
@@ -294,8 +302,13 @@ internal class Av1FrameBuffer : IDisposable
}
///
- /// Returns one logical row of 16-bit samples from the specified plane.
+ /// Gets one visible row of native 16-bit samples from a plane.
///
+ /// The luma or chroma plane.
+ /// The zero-based visible row index.
+ /// The horizontal chroma subsampling shift.
+ /// The vertical chroma subsampling shift.
+ /// The visible row without decoder padding.
public Span GetHighBitDepthRowSpan(Av1Plane plane, int row, int subX, int subY)
{
this.GetPlaneLayout(
@@ -312,6 +325,17 @@ internal class Av1FrameBuffer : IDisposable
return samples.Slice(originX, width);
}
+ ///
+ /// Resolves a plane allocation and its visible padded layout.
+ ///
+ /// The luma or chroma plane.
+ /// The horizontal chroma subsampling shift.
+ /// The vertical chroma subsampling shift.
+ /// Receives the selected plane allocation.
+ /// Receives the horizontal visible origin in plane samples.
+ /// Receives the vertical visible origin in plane samples.
+ /// Receives the visible plane width.
+ /// Receives the visible plane height.
private void GetPlaneLayout(
Av1Plane plane,
int subX,
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
index 3da724961..0e4c1bbf9 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
@@ -3,14 +3,22 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Provides the integer arithmetic primitives used by AV1 syntax and reconstruction.
+///
internal static class Av1Math
{
+ ///
+ /// Gets the zero-based position of the most significant set bit.
+ ///
+ /// A nonzero unsigned value.
+ /// The most significant set-bit position.
public static int MostSignificantBit(uint value)
{
int log = 0;
int i;
- Guard.IsTrue(value != 0, nameof(value), "Must have al least 1 bit set");
+ Guard.IsTrue(value != 0, nameof(value), "Must have at least one bit set.");
for (i = 4; i >= 0; --i)
{
@@ -26,6 +34,11 @@ internal static class Av1Math
return log;
}
+ ///
+ /// Gets the integer base-two logarithm of a positive value.
+ ///
+ /// The value.
+ /// The zero-based position of the most significant set bit.
public static int Log2(int n)
{
int result = 0;
@@ -38,10 +51,10 @@ internal static class Av1Math
}
///
- /// Long Log 2
- /// This is a quick adaptation of a Number
- /// Leading Zeros(NLZ) algorithm to get the log2f of a 32-bit number
+ /// Gets the integer base-two logarithm of an unsigned 32-bit value.
///
+ /// The value.
+ /// The zero-based position of the most significant set bit.
internal static uint Log2_32(uint x)
{
uint log = 0;
@@ -60,6 +73,11 @@ internal static class Av1Math
return log;
}
+ ///
+ /// Gets the greatest integer less than or equal to the base-two logarithm of a nonzero value.
+ ///
+ /// The nonzero value.
+ /// The floor of the base-two logarithm.
public static uint FloorLog2(uint value)
{
uint s = 0;
@@ -72,6 +90,11 @@ internal static class Av1Math
return s - 1;
}
+ ///
+ /// Gets the least integer greater than or equal to the base-two logarithm of a value.
+ ///
+ /// The value.
+ /// The ceiling of the base-two logarithm, or zero for values below two.
public static uint CeilLog2(uint value)
{
if (value < 2)
@@ -90,13 +113,39 @@ internal static class Av1Math
return i;
}
+ ///
+ /// Clips an unsigned sample to the range represented by a bit depth.
+ ///
+ /// The sample value.
+ /// The number of sample bits.
+ /// The clipped sample.
public static uint Clip1(uint value, int bitDepth) =>
Clip3(0, (1U << bitDepth) - 1, value);
+ ///
+ /// Clips an unsigned value to an inclusive range.
+ ///
+ /// The inclusive lower bound.
+ /// The inclusive upper bound.
+ /// The value to clip.
+ /// The clipped value.
public static uint Clip3(uint min, uint max, uint value) => Math.Max(min, Math.Min(max, value));
+ ///
+ /// Clips a signed value to an inclusive range.
+ ///
+ /// The inclusive lower bound.
+ /// The inclusive upper bound.
+ /// The value to clip.
+ /// The clipped value.
public static int Clip3(int min, int max, int value) => Math.Max(min, Math.Min(max, value));
+ ///
+ /// Divides an unsigned value by a power of two with nearest-integer rounding.
+ ///
+ /// The value.
+ /// The base-two divisor exponent.
+ /// The rounded quotient.
public static uint Round2(uint value, int n)
{
if (n == 0)
@@ -107,6 +156,12 @@ internal static class Av1Math
return (uint)((value + (1 << (n - 1))) >> n);
}
+ ///
+ /// Divides the absolute magnitude of a signed value by a power of two with nearest-integer rounding.
+ ///
+ /// The signed value.
+ /// The base-two divisor exponent.
+ /// The rounded nonnegative magnitude.
public static int Round2(int value, int n)
{
if (value < 0)
@@ -117,37 +172,102 @@ internal static class Av1Math
return (int)Round2((uint)value, n);
}
+ ///
+ /// Aligns a value upward to a multiple of a power of two.
+ ///
+ /// The value to align.
+ /// The base-two alignment exponent.
+ /// The aligned value.
internal static int AlignPowerOf2(int value, int n)
{
int mask = (1 << n) - 1;
return (value + mask) & ~mask;
}
+ ///
+ /// Divides a value by a power of two with nearest-integer rounding.
+ ///
+ /// The value.
+ /// The base-two divisor exponent.
+ /// The rounded quotient.
internal static int RoundPowerOf2(int value, int n) => (value + ((1 << n) >> 1)) >> n;
+ ///
+ /// Clamps a signed integer to an inclusive range.
+ ///
+ /// The value to clamp.
+ /// The inclusive lower bound.
+ /// The inclusive upper bound.
+ /// The clamped value.
internal static int Clamp(int value, int low, int high)
=> Math.Max(low, Math.Min(high, value));
+ ///
+ /// Clamps a signed long integer to an inclusive range.
+ ///
+ /// The value to clamp.
+ /// The inclusive lower bound.
+ /// The inclusive upper bound.
+ /// The clamped value.
internal static long Clamp(long value, long low, long high)
=> Math.Max(low, Math.Min(high, value));
+ ///
+ /// Divides a value by a power of two with floor rounding.
+ ///
+ /// The value.
+ /// The base-two divisor exponent.
+ /// The floor-rounded quotient.
internal static int DivideLog2Floor(int value, int n)
=> value >> n;
+ ///
+ /// Divides a nonnegative value by a power of two with ceiling rounding.
+ ///
+ /// The value.
+ /// The base-two divisor exponent.
+ /// The ceiling-rounded quotient.
internal static int DivideLog2Ceiling(int value, int n)
=> (value + (1 << n) - 1) >> n;
+ ///
+ /// Divides a value by a power of two with nearest-integer rounding.
+ ///
+ /// The value.
+ /// The base-two divisor exponent.
+ /// The rounded quotient.
internal static int DivideRound(int value, int bitCount)
=> (value + (1 << (bitCount - 1))) >> bitCount;
- // Last 3 bits are the value of mod 8.
+ ///
+ /// Gets the nonnegative remainder after division by eight.
+ ///
+ /// The value.
+ /// The low three bits of the value.
internal static int Modulus8(int value) => value & 0x07;
+ ///
+ /// Divides a value by eight with floor rounding.
+ ///
+ /// The value.
+ /// The floor-rounded quotient.
internal static int DivideBy8Floor(int value) => value >> 3;
+ ///
+ /// Divides a signed value by a power of two with symmetric nearest-integer rounding.
+ ///
+ /// The signed value.
+ /// The base-two divisor exponent.
+ /// The signed rounded quotient.
internal static int RoundPowerOf2Signed(int value, int n)
=> (value < 0) ? -RoundPowerOf2(-value, n) : RoundPowerOf2(value, n);
+ ///
+ /// Right-shifts a long intermediate with nearest-integer rounding.
+ ///
+ /// The value.
+ /// The positive shift count.
+ /// The rounded signed result.
internal static int RoundShift(long value, int bit)
{
DebugGuard.MustBeGreaterThanOrEqualTo(bit, 1, nameof(bit));
@@ -155,15 +275,35 @@ internal static class Av1Math
}
///
- /// implies .
+ /// Evaluates logical implication from one Boolean condition to another.
///
+ /// The antecedent.
+ /// The consequent.
+ /// only when is true and is false.
internal static bool Implies(bool a, bool b) => !a || b;
+ ///
+ /// Gets one bit from an integer value.
+ ///
+ /// The value.
+ /// The zero-based bit position.
+ /// Zero or one.
internal static int GetBit(int value, int n)
=> (value & (1 << n)) >> n;
+ ///
+ /// Sets one bit in an integer value.
+ ///
+ /// The value to update.
+ /// The zero-based bit position.
internal static void SetBit(ref int endOfBlockExtra, int n)
=> endOfBlockExtra |= 1 << n;
+ ///
+ /// Gets the absolute difference between two integers.
+ ///
+ /// The first value.
+ /// The second value.
+ /// The nonnegative absolute difference.
internal static int AbsoluteDifference(int a, int b) => (a > b) ? a - b : b - a;
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs
index 11f973a06..8941f191e 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs
@@ -3,10 +3,11 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Identifies the subdivision pattern applied to an AV1 coding block.
+///
internal enum Av1PartitionType
{
- // See section 6.10.4 of Avi Spcification
-
///
/// Not partitioned any further.
///
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs
index 99ba78c3b..546e6e94e 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs
@@ -3,8 +3,14 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Provides child-block geometry for AV1 partition types.
+///
internal static class Av1PartitionTypeExtensions
{
+ ///
+ /// Maps each partition type and parent block size to the size of its component blocks.
+ ///
private static readonly Av1BlockSize[][] PartitionSubSize = [
[
Av1BlockSize.Block4x4,
@@ -99,6 +105,12 @@ internal static class Av1PartitionTypeExtensions
]
];
+ ///
+ /// Gets the component block size produced by a partition operation.
+ ///
+ /// The partition operation.
+ /// The parent block size.
+ /// The component block size, or when the partition is not permitted.
public static Av1BlockSize GetBlockSubSize(this Av1PartitionType partition, Av1BlockSize blockSize)
=> PartitionSubSize[(int)partition][(int)blockSize];
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs
index 9d73cda42..fed90934a 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs
@@ -3,9 +3,23 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Identifies an AV1 luma or chroma sample plane.
+///
internal enum Av1Plane : int
{
+ ///
+ /// The luma plane.
+ ///
Y = 0,
+
+ ///
+ /// The first chroma plane.
+ ///
U = 1,
+
+ ///
+ /// The second chroma plane.
+ ///
V = 2,
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs
index e7ba0d62e..c3c6d214e 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs
@@ -10,14 +10,34 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+///
+/// Converts between reconstructed AV1 YUV planes and packed ImageSharp pixels.
+///
internal static class Av1YuvConverter
{
+ ///
+ /// The largest value represented by an eight-bit packed RGB component.
+ ///
private const float ByteMaximum = byte.MaxValue;
+ ///
+ /// Identifies the matrix operation used between encoded planes and RGB components.
+ ///
private enum ConversionMode
{
+ ///
+ /// A coefficient-based YCbCr matrix conversion.
+ ///
Coefficients,
+
+ ///
+ /// Direct G, B, and R component mapping from the Y, U, and V planes.
+ ///
Identity,
+
+ ///
+ /// The reversible-style YCgCo color transform.
+ ///
YCgCo,
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs
index c01fe3090..45195f166 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs
@@ -1792,7 +1792,7 @@ internal static class Av1DefaultDistributions
];
///
- /// Gets the end-of-block extra-bit distributions indexed by quantizer, transform-size, plane, and token contexts.
+ /// Gets the end-of-block extra-bit distributions indexed by quantizer, transform-size, plane, and padded token contexts.
///
private static Av1Distribution[][][][] EndOfBlockExtra =>
[
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
index 0597b63fd..73d56bdeb 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
@@ -612,8 +612,9 @@ internal ref struct Av1SymbolDecoder
int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint];
if (endOfBlockShift > 0)
{
- // Extra-bit distributions start with token three because the first three tokens have no extra bits.
- int endOfBlockContext = endOfBlockPoint - 3;
+ // The local table retains placeholders for the first three tokens, unlike libaom's compact table,
+ // so the decoded token is also the distribution index.
+ int endOfBlockContext = endOfBlockPoint;
bool bit = this.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext);
if (bit)
{
@@ -788,7 +789,7 @@ internal ref struct Av1SymbolDecoder
///
/// The square transform-size probability context.
/// The luma or chroma plane category.
- /// The zero-based extra-bit token context.
+ /// The token-aligned extra-bit context in the padded local table.
/// The decoded suffix bit.
private bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext)
{
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
index 8090c7045..57ea924dd 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
@@ -355,7 +355,10 @@ internal class Av1SymbolEncoder : IDisposable
ref Av1SymbolWriter w = ref this.writer;
int eobShift = eobOffsetBitCount - 1;
int bit = Av1Math.GetBit(eobExtra, eobShift);
- int endOfBlockContext = endOfBlockPosition - 3;
+
+ // The local table retains placeholders for the first three tokens, unlike libaom's compact table,
+ // so the encoded token is also the distribution index.
+ int endOfBlockContext = endOfBlockPosition;
w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockContext]);
for (int i = 1; i < eobOffsetBitCount; i++)
{
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
index 6965f31a6..de8d24528 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
@@ -989,7 +989,7 @@ internal class ObuReader
}
else
{
- frameHeader.PrimaryReferenceFrame = reader.ReadLiteral(Av1Constants.PimaryReferenceBits);
+ frameHeader.PrimaryReferenceFrame = reader.ReadLiteral(Av1Constants.PrimaryReferenceBits);
}
// Skipping, as no decoder info model present
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
index f718eedd6..9cc445968 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
@@ -68,7 +68,9 @@ internal class ObuWriter
private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span payload)
{
stream.WriteByte(WriteObuHeader(type));
- Span lengthBytes = stackalloc byte[3];
+
+ // A 32-bit OBU payload length requires at most five base-128 bytes.
+ Span lengthBytes = stackalloc byte[5];
int lengthLength = Av1BitStreamWriter.GetLittleEndianBytes128((uint)payload.Length, lengthBytes);
stream.Write(lengthBytes, 0, lengthLength);
stream.Write(payload);