From 90bb912a4c114beacb74613f4989745cc7655ee4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 24 Aug 2026 22:50:25 +1000 Subject: [PATCH] Document and correct AV1 core primitives --- .../Formats/Heif/Av1/Av1BitDepth.cs | 14 ++ .../Formats/Heif/Av1/Av1BitDepthExtensions.cs | 8 + .../Formats/Heif/Av1/Av1BitStreamReader.cs | 71 +++++++- .../Formats/Heif/Av1/Av1BitStreamWriter.cs | 131 ++++++++++++--- .../Formats/Heif/Av1/Av1BlockSize.cs | 21 ++- .../Heif/Av1/Av1BlockSizeExtensions.cs | 92 +++++++++-- .../Formats/Heif/Av1/Av1CodecConfiguration.cs | 46 +++++- .../Formats/Heif/Av1/Av1ColorFormat.cs | 18 +++ .../Formats/Heif/Av1/Av1Constants.cs | 148 +++++++++++++---- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 42 ++++- .../Formats/Heif/Av1/Av1FrameBuffer.cs | 152 ++++++++++-------- src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 152 +++++++++++++++++- .../Formats/Heif/Av1/Av1PartitionType.cs | 5 +- .../Heif/Av1/Av1PartitionTypeExtensions.cs | 12 ++ src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs | 14 ++ .../Formats/Heif/Av1/Av1YuvConverter.cs | 20 +++ .../Av1/Entropy/Av1DefaultDistributions.cs | 2 +- .../Heif/Av1/Entropy/Av1SymbolDecoder.cs | 7 +- .../Heif/Av1/Entropy/Av1SymbolEncoder.cs | 5 +- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 4 +- 21 files changed, 806 insertions(+), 160 deletions(-) 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);