Browse Source

Document and correct AV1 core primitives

pull/2633/head
James Jackson-South 1 week ago
parent
commit
90bb912a4c
  1. 14
      src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs
  2. 8
      src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs
  3. 71
      src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs
  4. 131
      src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
  5. 21
      src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs
  6. 92
      src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
  7. 46
      src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs
  8. 18
      src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs
  9. 148
      src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
  10. 42
      src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
  11. 152
      src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs
  12. 152
      src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
  13. 5
      src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs
  14. 12
      src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs
  15. 14
      src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs
  16. 20
      src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs
  17. 2
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs
  18. 7
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
  19. 5
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
  20. 2
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
  21. 4
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs

14
src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs

@ -3,9 +3,23 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Identifies the sample precision of an AV1 sequence.
/// </summary>
internal enum Av1BitDepth : int internal enum Av1BitDepth : int
{ {
/// <summary>
/// Eight bits per sample.
/// </summary>
EightBit = 0, EightBit = 0,
/// <summary>
/// Ten bits per sample.
/// </summary>
TenBit = 1, TenBit = 1,
/// <summary>
/// Twelve bits per sample.
/// </summary>
TwelveBit = 2, TwelveBit = 2,
} }

8
src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs

@ -5,7 +5,15 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Provides sample-precision conversions for AV1 bit-depth values.
/// </summary>
internal static class Av1BitDepthExtensions internal static class Av1BitDepthExtensions
{ {
/// <summary>
/// Gets the number of bits represented by an AV1 bit-depth value.
/// </summary>
/// <param name="bitDepth">The AV1 bit-depth value.</param>
/// <returns>Eight, ten, or twelve.</returns>
public static int GetBitCount(this Av1BitDepth bitDepth) => 8 + ((int)bitDepth << 1); public static int GetBitCount(this Av1BitDepth bitDepth) => 8 + ((int)bitDepth << 1);
} }

71
src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs

@ -3,23 +3,48 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Reads AV1 fixed-width and variable-length syntax from a most-significant-bit-first byte span.
/// </summary>
internal ref struct Av1BitStreamReader internal ref struct Av1BitStreamReader
{ {
/// <summary>
/// The complete encoded byte span.
/// </summary>
private readonly Span<byte> data; private readonly Span<byte> data;
/// <summary>
/// Initializes a new instance of the <see cref="Av1BitStreamReader"/> struct.
/// </summary>
/// <param name="data">The encoded AV1 data.</param>
public Av1BitStreamReader(Span<byte> data) => this.data = data; public Av1BitStreamReader(Span<byte> data) => this.data = data;
/// <summary>
/// Gets the zero-based position of the next bit to read.
/// </summary>
public int BitPosition { get; private set; } = 0; public int BitPosition { get; private set; } = 0;
/// <summary> /// <summary>
/// Gets the number of bytes in the readers buffer. /// Gets the number of bytes in the reader's buffer.
/// </summary> /// </summary>
public readonly int Length => this.data.Length; public readonly int Length => this.data.Length;
/// <summary>
/// Moves the next read position to the beginning of the buffer.
/// </summary>
public void Reset() => this.BitPosition = 0; public void Reset() => this.BitPosition = 0;
/// <summary>
/// Advances the read position without interpreting the skipped bits.
/// </summary>
/// <param name="bitCount">The number of bits to skip.</param>
public void Skip(int bitCount) => this.BitPosition += bitCount; public void Skip(int bitCount) => this.BitPosition += bitCount;
/// <summary>
/// Reads an unsigned fixed-width value in most-significant-bit-first order.
/// </summary>
/// <param name="bitCount">The number of bits to read.</param>
/// <returns>The decoded unsigned value.</returns>
public uint ReadLiteral(int bitCount) public uint ReadLiteral(int bitCount)
{ {
DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount)); DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount));
@ -33,6 +58,10 @@ internal ref struct Av1BitStreamReader
return literal; return literal;
} }
/// <summary>
/// Reads the next encoded bit.
/// </summary>
/// <returns>Zero or one.</returns>
internal uint ReadBit() internal uint ReadBit()
{ {
int byteOffset = Av1Math.DivideBy8Floor(this.BitPosition); int byteOffset = Av1Math.DivideBy8Floor(this.BitPosition);
@ -41,11 +70,19 @@ internal ref struct Av1BitStreamReader
return (uint)((this.data[byteOffset] >> shift) & 0x01); return (uint)((this.data[byteOffset] >> shift) & 0x01);
} }
/// <summary>
/// Reads the next encoded bit as a Boolean value.
/// </summary>
/// <returns><see langword="true"/> for one; otherwise, <see langword="false"/>.</returns>
internal bool ReadBoolean() => this.ReadLiteral(1) > 0; internal bool ReadBoolean() => this.ReadLiteral(1) > 0;
/// <summary>
/// Reads an AV1 little-endian base-128 value from a byte-aligned position.
/// </summary>
/// <param name="length">Receives the number of encoded bytes consumed.</param>
/// <returns>The decoded unsigned value.</returns>
public ulong ReadLittleEndianBytes128(out int length) 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})."); DebugGuard.IsTrue((this.BitPosition & 0x07) == 0, $"Reading of Little Endian 128 value only allowed on byte alignment (offset {this.BitPosition}).");
ulong value = 0; ulong value = 0;
@ -64,9 +101,12 @@ internal ref struct Av1BitStreamReader
return value; return value;
} }
/// <summary>
/// Reads the AV1 unsigned-variable-length code.
/// </summary>
/// <returns>The decoded unsigned value.</returns>
public uint ReadUnsignedVariableLength() public uint ReadUnsignedVariableLength()
{ {
// See section 4.10.3 of the AV1-Specification
int leadingZerosCount = 0; int leadingZerosCount = 0;
while (leadingZerosCount < 32) while (leadingZerosCount < 32)
{ {
@ -94,9 +134,13 @@ internal ref struct Av1BitStreamReader
return 0; return 0;
} }
/// <summary>
/// Reads a value from an alphabet whose size is not a power of two.
/// </summary>
/// <param name="n">The number of symbols in the alphabet.</param>
/// <returns>A decoded symbol in the range zero through <paramref name="n"/> minus one.</returns>
public uint ReadNonSymmetric(uint n) public uint ReadNonSymmetric(uint n)
{ {
// See section 4.10.7 of the AV1-Specification
if (n <= 1) if (n <= 1)
{ {
return 0; return 0;
@ -113,15 +157,19 @@ internal ref struct Av1BitStreamReader
return (v << 1) - m + this.ReadLiteral(1); return (v << 1) - m + this.ReadLiteral(1);
} }
/// <summary>
/// Reads a fixed-width two's-complement signed integer.
/// </summary>
/// <param name="n">The encoded bit width.</param>
/// <returns>The sign-extended integer.</returns>
public int ReadSignedFromUnsigned(int n) public int ReadSignedFromUnsigned(int n)
{ {
// See section 4.10.6 of the AV1-Specification
int signedValue; int signedValue;
uint value = this.ReadLiteral(n); uint value = this.ReadLiteral(n);
uint signMask = 1U << (n - 1); uint signMask = 1U << (n - 1);
if ((value & signMask) == signMask) 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)); signedValue = (int)((long)value - (signMask << 1));
} }
else else
@ -132,9 +180,13 @@ internal ref struct Av1BitStreamReader
return signedValue; return signedValue;
} }
/// <summary>
/// Reads a byte-aligned unsigned integer whose least-significant byte is encoded first.
/// </summary>
/// <param name="n">The number of bytes to read.</param>
/// <returns>The decoded unsigned integer.</returns>
public uint ReadLittleEndian(int n) 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"); DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Reading of Little Endian value only allowed on byte alignment");
uint t = 0; uint t = 0;
@ -146,6 +198,11 @@ internal ref struct Av1BitStreamReader
return t; return t;
} }
/// <summary>
/// Gets a byte-aligned tile payload for entropy decoding and advances past it.
/// </summary>
/// <param name="tileDataSize">The tile payload length in bytes.</param>
/// <returns>The tile payload span.</returns>
public Span<byte> GetSymbolReader(int tileDataSize) public Span<byte> GetSymbolReader(int tileDataSize)
{ {
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Symbol reading needs to start on byte boundary."); DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Symbol reading needs to start on byte boundary.");

131
src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs

@ -5,14 +5,40 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Writes AV1 fixed-width and variable-length syntax to reusable expanding memory.
/// </summary>
internal ref struct Av1BitStreamWriter internal ref struct Av1BitStreamWriter
{ {
/// <summary>
/// The number of bits in one output byte.
/// </summary>
private const int WordSize = 8; private const int WordSize = 8;
/// <summary>
/// The expanding output allocation.
/// </summary>
private readonly AutoExpandingMemory<byte> memory; private readonly AutoExpandingMemory<byte> memory;
/// <summary>
/// The current writable view over <see cref="memory"/>.
/// </summary>
private Span<byte> span; private Span<byte> span;
/// <summary>
/// The final byte index that can be written without expanding <see cref="memory"/>.
/// </summary>
private int capacityTrigger; private int capacityTrigger;
/// <summary>
/// The partially assembled output byte.
/// </summary>
private byte buffer = 0; private byte buffer = 0;
/// <summary>
/// Initializes a new instance of the <see cref="Av1BitStreamWriter"/> struct.
/// </summary>
/// <param name="memory">The reusable expanding output allocation.</param>
public Av1BitStreamWriter(AutoExpandingMemory<byte> memory) public Av1BitStreamWriter(AutoExpandingMemory<byte> memory)
{ {
this.memory = memory; this.memory = memory;
@ -20,36 +46,45 @@ internal ref struct Av1BitStreamWriter
this.capacityTrigger = memory.Capacity - 1; this.capacityTrigger = memory.Capacity - 1;
} }
/// <summary>
/// Gets the zero-based position of the next output bit.
/// </summary>
public int BitPosition { get; private set; } = 0; public int BitPosition { get; private set; } = 0;
/// <summary>
/// Gets the current output capacity in bytes.
/// </summary>
public readonly int Capacity => this.memory.Capacity; public readonly int Capacity => this.memory.Capacity;
/// <summary>
/// Encodes an unsigned 32-bit value using little-endian base-128 bytes.
/// </summary>
/// <param name="value">The value to encode.</param>
/// <param name="span">The destination receiving up to five bytes.</param>
/// <returns>The number of bytes written.</returns>
public static int GetLittleEndianBytes128(uint value, Span<byte> span) public static int GetLittleEndianBytes128(uint value, Span<byte> span)
{ {
if (value < 0x80U) int length = 0;
{ do
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)
{ {
span[0] = (byte)((value & 0x7fU) | 0x80U); byte encodedByte = (byte)(value & 0x7fU);
span[1] = (byte)((value >> 7) & 0xff); value >>= 7;
span[2] = (byte)((value >> 14) & 0xff); if (value != 0)
return 3; {
} encodedByte |= 0x80;
else }
{
throw new NotImplementedException("No such large values yet."); span[length++] = encodedByte;
} }
while (value != 0);
return length;
} }
/// <summary>
/// Advances the output position, emitting the current byte whenever the skip crosses a byte boundary.
/// </summary>
/// <param name="bitCount">The number of bits to skip.</param>
public void Skip(int bitCount) public void Skip(int bitCount)
{ {
this.BitPosition += bitCount; this.BitPosition += bitCount;
@ -60,6 +95,9 @@ internal ref struct Av1BitStreamWriter
} }
} }
/// <summary>
/// Writes a partially assembled byte and resets the position for output-memory reuse.
/// </summary>
public void Flush() public void Flush()
{ {
if (Av1Math.Modulus8(this.BitPosition) != 0) if (Av1Math.Modulus8(this.BitPosition) != 0)
@ -71,6 +109,11 @@ internal ref struct Av1BitStreamWriter
this.BitPosition = 0; this.BitPosition = 0;
} }
/// <summary>
/// Writes an unsigned fixed-width value in most-significant-bit-first order.
/// </summary>
/// <param name="value">The value to write.</param>
/// <param name="bitCount">The number of low-order bits to write.</param>
public void WriteLiteral(uint value, int bitCount) public void WriteLiteral(uint value, int bitCount)
{ {
for (int bit = bitCount - 1; bit >= 0; bit--) for (int bit = bitCount - 1; bit >= 0; bit--)
@ -79,15 +122,23 @@ internal ref struct Av1BitStreamWriter
} }
} }
/// <summary>
/// Writes one Boolean bit.
/// </summary>
/// <param name="value">The Boolean value.</param>
internal void WriteBoolean(bool value) internal void WriteBoolean(bool value)
{ {
byte boolByte = value ? (byte)1 : (byte)0; byte boolByte = value ? (byte)1 : (byte)0;
this.WriteBit(boolByte); this.WriteBit(boolByte);
} }
/// <summary>
/// Writes a fixed-width signed integer in two's-complement form.
/// </summary>
/// <param name="signedValue">The signed value.</param>
/// <param name="n">The encoded bit width.</param>
public void WriteSignedFromUnsigned(int signedValue, int n) public void WriteSignedFromUnsigned(int signedValue, int n)
{ {
// See section 4.10.6 of the AV1-Specification
ulong value = (ulong)signedValue; ulong value = (ulong)signedValue;
if (signedValue < 0) if (signedValue < 0)
{ {
@ -97,15 +148,32 @@ internal ref struct Av1BitStreamWriter
this.WriteLiteral((uint)value, n); this.WriteLiteral((uint)value, n);
} }
/// <summary>
/// Writes an unsigned 32-bit value using little-endian base-128 bytes.
/// </summary>
/// <param name="value">The value to write.</param>
public void WriteLittleEndianBytes128(uint value) 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; this.BitPosition += bytesWritten << 3;
} }
/// <summary>
/// Writes a value from an alphabet whose size is not a power of two.
/// </summary>
/// <param name="value">The symbol value.</param>
/// <param name="numberOfSymbols">The number of symbols in the alphabet.</param>
internal void WriteNonSymmetric(uint value, uint numberOfSymbols) internal void WriteNonSymmetric(uint value, uint numberOfSymbols)
{ {
// See section 4.10.7 of the AV1-Specification
if (numberOfSymbols <= 1) if (numberOfSymbols <= 1)
{ {
return; return;
@ -126,6 +194,10 @@ internal ref struct Av1BitStreamWriter
} }
} }
/// <summary>
/// Appends one bit to the partially assembled output byte.
/// </summary>
/// <param name="value">Zero or one.</param>
private void WriteBit(byte value) private void WriteBit(byte value)
{ {
int bit = this.BitPosition & 0x07; int bit = this.BitPosition & 0x07;
@ -138,9 +210,13 @@ internal ref struct Av1BitStreamWriter
this.BitPosition++; this.BitPosition++;
} }
/// <summary>
/// Writes an unsigned integer with its least-significant byte first.
/// </summary>
/// <param name="value">The value to write.</param>
/// <param name="n">The number of bytes to write.</param>
public void WriteLittleEndian(uint value, int n) 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"); DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Little Endian value only allowed on byte alignment");
uint t = value; uint t = value;
@ -151,6 +227,10 @@ internal ref struct Av1BitStreamWriter
} }
} }
/// <summary>
/// Writes a byte-aligned entropy-coded tile payload.
/// </summary>
/// <param name="tileData">The tile payload.</param>
internal void WriteBlob(Span<byte> tileData) internal void WriteBlob(Span<byte> tileData)
{ {
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Tile Data only allowed on byte alignment"); 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; this.BitPosition += tileData.Length << 3;
} }
/// <summary>
/// Stores the current output byte, expanding the allocation when necessary.
/// </summary>
private void WriteBuffer() private void WriteBuffer()
{ {
int wordPosition = Av1Math.DivideBy8Floor(this.BitPosition); int wordPosition = Av1Math.DivideBy8Floor(this.BitPosition);

21
src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs

@ -3,10 +3,11 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Identifies every luma block size defined by AV1.
/// </summary>
internal enum Av1BlockSize : byte internal enum Av1BlockSize : byte
{ {
// See sction 6.10.4 of the Av1 Specification.
/// <summary>A block of samples, 4 samples wide and 4 samples high.</summary> /// <summary>A block of samples, 4 samples wide and 4 samples high.</summary>
Block4x4 = 0, Block4x4 = 0,
@ -72,8 +73,24 @@ internal enum Av1BlockSize : byte
/// <summary>A block of samples, 64 samples wide and 16 samples high.</summary> /// <summary>A block of samples, 64 samples wide and 16 samples high.</summary>
Block64x16 = 21, Block64x16 = 21,
/// <summary>
/// The number of concrete block-size values.
/// </summary>
AllSizes = 22, AllSizes = 22,
/// <summary>
/// The first extended rectangular block size following the primary size set.
/// </summary>
SizeS = Block4x16, SizeS = Block4x16,
/// <summary>
/// A sentinel representing an invalid block size.
/// </summary>
Invalid = 255, Invalid = 255,
/// <summary>
/// The final value in the primary block-size set.
/// </summary>
Largest = SizeS - 1, Largest = SizeS - 1,
} }

92
src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs

@ -5,12 +5,24 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Provides dimensions, chroma subsampling, and transform limits for AV1 block sizes.
/// </summary>
internal static class Av1BlockSizeExtensions internal static class Av1BlockSizeExtensions
{ {
/// <summary>
/// The width of each block size in units of four samples.
/// </summary>
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]; 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];
/// <summary>
/// The height of each block size in units of four samples.
/// </summary>
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]; 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). /// <summary>
/// Maps each luma block size and pair of chroma subsampling shifts to its residual-plane block size.
/// </summary>
private static readonly Av1BlockSize[][][] SubSampled = private static readonly Av1BlockSize[][][] SubSampled =
[ [
@ -40,6 +52,9 @@ internal static class Av1BlockSizeExtensions
[[Av1BlockSize.Block64x16, Av1BlockSize.Invalid], [Av1BlockSize.Block32x16, Av1BlockSize.Block32x8]] [[Av1BlockSize.Block64x16, Av1BlockSize.Invalid], [Av1BlockSize.Block32x16, Av1BlockSize.Block32x8]]
]; ];
/// <summary>
/// Maps each block size to its largest permitted transform size.
/// </summary>
private static readonly Av1TransformSize[] MaxTransformSize = [ private static readonly Av1TransformSize[] MaxTransformSize = [
Av1TransformSize.Size4x4, Av1TransformSize.Size4x8, Av1TransformSize.Size8x4, Av1TransformSize.Size8x8, Av1TransformSize.Size4x4, Av1TransformSize.Size4x8, Av1TransformSize.Size8x4, Av1TransformSize.Size8x8,
Av1TransformSize.Size8x16, Av1TransformSize.Size16x8, Av1TransformSize.Size16x16, Av1TransformSize.Size16x32, Av1TransformSize.Size8x16, Av1TransformSize.Size16x8, Av1TransformSize.Size16x16, Av1TransformSize.Size16x32,
@ -49,9 +64,15 @@ internal static class Av1BlockSizeExtensions
Av1TransformSize.Size16x64, Av1TransformSize.Size64x16 Av1TransformSize.Size16x64, Av1TransformSize.Size64x16
]; ];
/// <summary>
/// Contains the base-two logarithm of the sample count for each block size.
/// </summary>
private static readonly int[] PelsLog2Count = 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]; [4, 5, 5, 6, 7, 7, 8, 9, 9, 10, 11, 11, 12, 13, 13, 14, 6, 6, 8, 8, 10, 10];
/// <summary>
/// Maps geometry dimension logarithms to an AV1 block size using the mode-decision scan's transposed axis convention.
/// </summary>
private static readonly Av1BlockSize[][] HeightWidthToSize = [ private static readonly Av1BlockSize[][] HeightWidthToSize = [
[Av1BlockSize.Block4x4, Av1BlockSize.Block4x8, Av1BlockSize.Block4x16, Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid], [Av1BlockSize.Block4x4, Av1BlockSize.Block4x8, Av1BlockSize.Block4x16, Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid],
[Av1BlockSize.Block8x4, Av1BlockSize.Block8x8, Av1BlockSize.Block8x16, Av1BlockSize.Block8x32, 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] [Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64, Av1BlockSize.Block128x128]
]; ];
/// <summary>
/// Gets the block width in units of four samples.
/// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The number of four-sample columns.</returns>
public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize]; public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize];
/// <summary>
/// Gets the block height in units of four samples.
/// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The number of four-sample rows.</returns>
public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize]; public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize];
/// <summary> /// <summary>
/// Gets the <see cref="Av1BlockSize"/> given by the Log2 of the width and height. /// Gets the block size from mode-decision geometry dimension logarithms, where zero represents four samples.
/// </summary> /// </summary>
/// <param name="widthLog2">Log2 of the width value.</param> /// <param name="widthLog2">The base-two width logarithm minus two.</param>
/// <param name="heightLog2">Log2 of the height value.</param> /// <param name="heightLog2">The base-two height logarithm minus two.</param>
/// <returns>The <see cref="Av1BlockSize"/>.</returns> /// <returns>The matching block size, or <see cref="Av1BlockSize.Invalid"/> for unsupported dimensions.</returns>
public static Av1BlockSize FromWidthAndHeight(uint widthLog2, uint heightLog2) => HeightWidthToSize[heightLog2][widthLog2]; 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];
}
/// <summary> /// <summary>
/// Returns the width of the block in samples. /// Gets the block width in samples.
/// </summary> /// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The block width in samples.</returns>
public static int GetWidth(this Av1BlockSize blockSize) public static int GetWidth(this Av1BlockSize blockSize)
=> Get4x4WideCount(blockSize) << 2; => Get4x4WideCount(blockSize) << 2;
/// <summary> /// <summary>
/// Returns of the height of the block in 4 samples. /// Gets the block height in samples.
/// </summary> /// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The block height in samples.</returns>
public static int GetHeight(this Av1BlockSize blockSize) public static int GetHeight(this Av1BlockSize blockSize)
=> Get4x4HighCount(blockSize) << 2; => Get4x4HighCount(blockSize) << 2;
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The base-two logarithm of the four-sample column count.</returns>
public static int Get4x4WidthLog2(this Av1BlockSize blockSize) public static int Get4x4WidthLog2(this Av1BlockSize blockSize)
=> Av1Math.Log2(Get4x4WideCount(blockSize)); => Av1Math.Log2(Get4x4WideCount(blockSize));
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The base-two logarithm of the four-sample row count.</returns>
public static int Get4x4HeightLog2(this Av1BlockSize blockSize) public static int Get4x4HeightLog2(this Av1BlockSize blockSize)
=> Av1Math.Log2(Get4x4HighCount(blockSize)); => Av1Math.Log2(Get4x4HighCount(blockSize));
/// <summary> /// <summary>
/// Returns the block size of a sub sampled block. /// Gets the residual-plane block size for Boolean chroma subsampling flags.
/// </summary> /// </summary>
/// <param name="blockSize">The luma block size.</param>
/// <param name="subX">Indicates horizontal chroma subsampling.</param>
/// <param name="subY">Indicates vertical chroma subsampling.</param>
/// <returns>The corresponding residual-plane block size.</returns>
public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, bool subX, bool subY) public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, bool subX, bool subY)
=> GetSubsampled(blockSize, subX ? 1 : 0, subY ? 1 : 0); => GetSubsampled(blockSize, subX ? 1 : 0, subY ? 1 : 0);
/// <summary> /// <summary>
/// Returns the block size of a sub sampled block. /// Gets the residual-plane block size for chroma subsampling shifts.
/// </summary> /// </summary>
/// <param name="blockSize">The luma block size.</param>
/// <param name="subX">The horizontal chroma subsampling shift.</param>
/// <param name="subY">The vertical chroma subsampling shift.</param>
/// <returns>The corresponding residual-plane block size, or <see cref="Av1BlockSize.Invalid"/> when unavailable.</returns>
public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, int subX, int subY) public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, int subX, int subY)
{ {
if (blockSize == Av1BlockSize.Invalid) if (blockSize == Av1BlockSize.Invalid)
@ -116,6 +167,13 @@ internal static class Av1BlockSizeExtensions
return SubSampled[(int)blockSize][subX][subY]; return SubSampled[(int)blockSize][subX][subY];
} }
/// <summary>
/// Gets the maximum chroma transform size after applying plane subsampling and AV1 chroma transform limits.
/// </summary>
/// <param name="blockSize">The luma block size.</param>
/// <param name="subX">Indicates horizontal chroma subsampling.</param>
/// <param name="subY">Indicates vertical chroma subsampling.</param>
/// <returns>The maximum chroma transform size, or <see cref="Av1TransformSize.Invalid"/> when the plane block size is invalid.</returns>
public static Av1TransformSize GetMaxUvTransformSize(this Av1BlockSize blockSize, bool subX, bool subY) public static Av1TransformSize GetMaxUvTransformSize(this Av1BlockSize blockSize, bool subX, bool subY)
{ {
Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY); Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY);
@ -135,12 +193,18 @@ internal static class Av1BlockSizeExtensions
} }
/// <summary> /// <summary>
/// Returns the largest transform size that can be used for blocks of given size. /// Gets the largest square or rectangular transform size permitted for a block.
/// The can be either a square or rectangular block.
/// </summary> /// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The maximum transform size.</returns>
public static Av1TransformSize GetMaximumTransformSize(this Av1BlockSize blockSize) public static Av1TransformSize GetMaximumTransformSize(this Av1BlockSize blockSize)
=> MaxTransformSize[(int)blockSize]; => MaxTransformSize[(int)blockSize];
/// <summary>
/// Gets the base-two logarithm of the block's sample count.
/// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The base-two logarithm of width multiplied by height.</returns>
public static int GetPelsLog2Count(this Av1BlockSize blockSize) public static int GetPelsLog2Count(this Av1BlockSize blockSize)
=> PelsLog2Count[(int)blockSize]; => PelsLog2Count[(int)blockSize];
} }

46
src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs

@ -3,11 +3,14 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary> /// <summary>
/// Implementation of section 2.3.3 of AV1 Codec ISO Media File Format Binding specification v1.2.0. /// Represents the decoder configuration fields stored in an AV1 codec-configuration property.
/// See https://aomediacodec.github.io/av1-isobmff/v1.2.0.html#av1codecconfigurationbox-syntax.
/// </summary> /// </summary>
internal struct Av1CodecConfiguration internal struct Av1CodecConfiguration
{ {
/// <summary>
/// Initializes a new instance of the <see cref="Av1CodecConfiguration"/> struct from an AV1 codec-configuration payload.
/// </summary>
/// <param name="boxBuffer">The configuration payload beginning with the marker and version fields.</param>
public Av1CodecConfiguration(Span<byte> boxBuffer) public Av1CodecConfiguration(Span<byte> boxBuffer)
{ {
Av1BitStreamReader reader = new(boxBuffer); Av1BitStreamReader reader = new(boxBuffer);
@ -35,29 +38,68 @@ internal struct Av1CodecConfiguration
} }
} }
/// <summary>
/// Gets the one-bit configuration marker.
/// </summary>
public byte Marker { get; } public byte Marker { get; }
/// <summary>
/// Gets the codec-configuration record version.
/// </summary>
public byte Version { get; } public byte Version { get; }
/// <summary>
/// Gets the sequence profile declared by the configuration record.
/// </summary>
public byte SeqProfile { get; } public byte SeqProfile { get; }
/// <summary>
/// Gets the first operating point's sequence level index.
/// </summary>
public byte SeqLevelIdx0 { get; } public byte SeqLevelIdx0 { get; }
/// <summary>
/// Gets the first operating point's sequence tier flag.
/// </summary>
public byte SeqTier0 { get; } public byte SeqTier0 { get; }
/// <summary>
/// Gets the high-bit-depth flag.
/// </summary>
public byte HighBitdepth { get; } public byte HighBitdepth { get; }
/// <summary>
/// Gets a value indicating whether the sequence uses twelve-bit samples.
/// </summary>
public bool TwelveBit { get; } public bool TwelveBit { get; }
/// <summary>
/// Gets a value indicating whether the sequence contains only a luma plane.
/// </summary>
public bool MonoChrome { get; } public bool MonoChrome { get; }
/// <summary>
/// Gets a value indicating whether chroma is horizontally subsampled.
/// </summary>
public bool ChromaSubsamplingX { get; } public bool ChromaSubsamplingX { get; }
/// <summary>
/// Gets a value indicating whether chroma is vertically subsampled.
/// </summary>
public bool ChromaSubsamplingY { get; } public bool ChromaSubsamplingY { get; }
/// <summary>
/// Gets the chroma sample-position code.
/// </summary>
public byte ChromaSamplePosition { get; } public byte ChromaSamplePosition { get; }
/// <summary>
/// Gets a value indicating whether an initial presentation delay is declared.
/// </summary>
public bool InitialPresentationDelayPresent { get; } public bool InitialPresentationDelayPresent { get; }
/// <summary>
/// Gets the initial presentation delay in decoded frames, or zero when no delay is declared.
/// </summary>
public byte InitialPresentationDelay { get; } public byte InitialPresentationDelay { get; }
} }

18
src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs

@ -3,10 +3,28 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Identifies the AV1 luma and chroma plane sampling layout.
/// </summary>
internal enum Av1ColorFormat internal enum Av1ColorFormat
{ {
/// <summary>
/// Monochrome luma samples without chroma planes.
/// </summary>
Yuv400, Yuv400,
/// <summary>
/// Chroma samples subsampled by two horizontally and vertically.
/// </summary>
Yuv420, Yuv420,
/// <summary>
/// Chroma samples subsampled by two horizontally.
/// </summary>
Yuv422, Yuv422,
/// <summary>
/// Full-resolution luma and chroma samples.
/// </summary>
Yuv444, Yuv444,
} }

148
src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs

@ -6,195 +6,278 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Defines shared AV1 syntax, geometry, entropy, and transform limits.
/// </summary>
internal static class Av1Constants internal static class Av1Constants
{ {
/// <summary>
/// The highest sequence profile defined by AV1.
/// </summary>
public const ObuSequenceProfile MaxSequenceProfile = ObuSequenceProfile.Professional; public const ObuSequenceProfile MaxSequenceProfile = ObuSequenceProfile.Professional;
/// <summary>
/// The number of bits used for an operating-point level index.
/// </summary>
public const int LevelBits = 5; public const int LevelBits = 5;
/// <summary> /// <summary>
/// Number of fractional bits for computing position in upscaling. /// The number of bits used to signal a super-resolution denominator offset.
/// </summary> /// </summary>
public const int SuperResolutionScaleBits = 14; public const int SuperResolutionScaleBits = 3;
public const int ScaleNumerator = -1; /// <summary>
/// The fixed numerator of the AV1 super-resolution scaling ratio.
/// </summary>
public const int ScaleNumerator = 8;
/// <summary> /// <summary>
/// Number of reference frames that can be used for inter prediction. /// The number of reference frames that can be used for inter prediction.
/// </summary> /// </summary>
public const int ReferencesPerFrame = 7; public const int ReferencesPerFrame = 7;
/// <summary> /// <summary>
/// Maximum area of a tile in units of luma samples. /// The maximum area of a tile in units of luma samples.
/// </summary> /// </summary>
public const int MaxTileArea = 4096 * 2304; public const int MaxTileArea = 4096 * 2304;
/// <summary> /// <summary>
/// Maximum width of a tile in units of luma samples. /// The maximum width of a tile in units of luma samples.
/// </summary> /// </summary>
public const int MaxTileWidth = 4096; public const int MaxTileWidth = 4096;
/// <summary> /// <summary>
/// Maximum number of tile columns. /// The maximum number of tile columns.
/// </summary> /// </summary>
public const int MaxTileColumnCount = 64; public const int MaxTileColumnCount = 64;
/// <summary> /// <summary>
/// Maximum number of tile rows. /// The maximum number of tile rows.
/// </summary> /// </summary>
public const int MaxTileRowCount = 64; public const int MaxTileRowCount = 64;
/// <summary> /// <summary>
/// Number of frames that can be stored for future reference. /// The number of frames that can be stored for future reference.
/// </summary> /// </summary>
public const int ReferenceFrameCount = 8; public const int ReferenceFrameCount = 8;
/// <summary> /// <summary>
/// Value of 'PrimaryReferenceFrame' indicating that there is no primary reference frame. /// The primary-reference-frame value indicating that no primary reference is selected.
/// </summary> /// </summary>
public const uint PrimaryReferenceFrameNone = 7; public const uint PrimaryReferenceFrameNone = 7;
public const int PimaryReferenceBits = 3; /// <summary>
/// The number of bits used to signal a primary reference frame.
/// </summary>
public const int PrimaryReferenceBits = 3;
/// <summary> /// <summary>
/// Number of segments allowed in segmentation map. /// The number of segments allowed in a segmentation map.
/// </summary> /// </summary>
public const int MaxSegmentCount = 8; public const int MaxSegmentCount = 8;
/// <summary> /// <summary>
/// Smallest denominator for upscaling ratio. /// The smallest signaled denominator for an active super-resolution ratio.
/// </summary> /// </summary>
public const int SuperResolutionScaleDenominatorMinimum = 9; public const int SuperResolutionScaleDenominatorMinimum = 9;
/// <summary> /// <summary>
/// Base 2 logarithm of maximum size of a superblock in luma samples. /// The base-two logarithm of the maximum superblock size in luma samples.
/// </summary> /// </summary>
public const int MaxSuperBlockSizeLog2 = 7; public const int MaxSuperBlockSizeLog2 = 7;
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public const int ModeInfoSizeLog2 = 2; public const int ModeInfoSizeLog2 = 2;
/// <summary>
/// The maximum quantizer index.
/// </summary>
public const int MaxQ = 255; public const int MaxQ = 255;
/// <summary> /// <summary>
/// Number of segmentation features. /// The number of segmentation features.
/// </summary> /// </summary>
public const int SegmentationLevelMax = 8; public const int SegmentationLevelMax = 8;
/// <summary> /// <summary>
/// Maximum size of a loop restoration tile. /// The maximum loop-restoration tile size in samples.
/// </summary> /// </summary>
public const int RestorationMaxTileSize = 256; public const int RestorationMaxTileSize = 256;
/// <summary> /// <summary>
/// Number of Wiener coefficients to read. /// The number of independent Wiener filter coefficients per direction.
/// </summary> /// </summary>
public const int WienerCoefficientCount = 3; public const int WienerCoefficientCount = 3;
/// <summary>
/// The number of luma and chroma frame loop-filter levels.
/// </summary>
public const int FrameLoopFilterCount = 4; public const int FrameLoopFilterCount = 4;
/// <summary> /// <summary>
/// Value indicating alternative encoding of quantizer index delta values. /// The first quantizer-delta magnitude encoded through the escape path.
/// </summary> /// </summary>
public const int DeltaQuantizerSmall = 3; public const int DeltaQuantizerSmall = 3;
/// <summary> /// <summary>
/// Value indicating alternative encoding of loop filter delta values. /// The first loop-filter-delta magnitude encoded through the escape path.
/// </summary> /// </summary>
public const int DeltaLoopFilterSmall = 3; public const int DeltaLoopFilterSmall = 3;
/// <summary> /// <summary>
/// Maximum value used for loop filtering. /// The maximum loop-filter strength.
/// </summary> /// </summary>
public const int MaxLoopFilter = 63; public const int MaxLoopFilter = 63;
/// <summary> /// <summary>
/// Maximum magnitude of AngleDeltaY and AngleDeltaUV. /// The maximum directional-prediction angle-delta magnitude.
/// </summary> /// </summary>
public const int MaxAngleDelta = 3; public const int MaxAngleDelta = 3;
/// <summary> /// <summary>
/// Maximum number of color planes. /// The maximum number of color planes.
/// </summary> /// </summary>
public const int MaxPlanes = 3; public const int MaxPlanes = 3;
/// <summary> /// <summary>
/// Number of reference frame types (including intra type). /// The number of reference-frame types, including the intra type.
/// </summary> /// </summary>
public const int TotalReferencesPerFrame = 8; public const int TotalReferencesPerFrame = 8;
/// <summary> /// <summary>
/// Number of values for palette_size. /// The maximum palette size.
/// </summary> /// </summary>
public const int PaletteMaxSize = 8; public const int PaletteMaxSize = 8;
/// <summary> /// <summary>
/// Maximum transform size categories. /// The number of transform-size probability categories.
/// </summary> /// </summary>
public const int MaxTransformCategories = 4; public const int MaxTransformCategories = 4;
/// <summary>
/// The number of cumulative coefficient-level magnitude contexts.
/// </summary>
public const int CoefficientContextCount = 6; public const int CoefficientContextCount = 6;
/// <summary>
/// The number of coefficient magnitudes represented by base symbols before base-range coding.
/// </summary>
public const int BaseLevelsCount = 2; public const int BaseLevelsCount = 2;
/// <summary>
/// The maximum coefficient magnitude increment represented by base-range symbols.
/// </summary>
public const int CoefficientBaseRange = 12; public const int CoefficientBaseRange = 12;
/// <summary>
/// The maximum transform dimension in samples.
/// </summary>
public const int MaxTransformSize = 1 << 6; public const int MaxTransformSize = 1 << 6;
/// <summary>
/// The maximum transform dimension in units of four samples.
/// </summary>
public const int MaxTransformSizeUnit = MaxTransformSize >> 2; public const int MaxTransformSizeUnit = MaxTransformSize >> 2;
/// <summary>
/// The number of low-order bits reserved for a cumulative coefficient-level context.
/// </summary>
public const int CoefficientContextBitCount = 6; public const int CoefficientContextBitCount = 6;
/// <summary>
/// The mask selecting the cumulative coefficient-level magnitude bits.
/// </summary>
public const int CoefficientContextMask = (1 << CoefficientContextBitCount) - 1; public const int CoefficientContextMask = (1 << CoefficientContextBitCount) - 1;
/// <summary>
/// The base-two logarithm of the horizontal coefficient-context padding.
/// </summary>
public const int TransformPadHorizontalLog2 = 2; public const int TransformPadHorizontalLog2 = 2;
/// <summary>
/// The horizontal coefficient-context padding in elements.
/// </summary>
public const int TransformPadHorizontal = 1 << TransformPadHorizontalLog2; public const int TransformPadHorizontal = 1 << TransformPadHorizontalLog2;
/// <summary>
/// The total vertical coefficient-context padding in rows.
/// </summary>
public const int TransformPadVertical = 6; public const int TransformPadVertical = 6;
/// <summary>
/// The trailing coefficient-context padding in elements.
/// </summary>
public const int TransformPadEnd = 16; public const int TransformPadEnd = 16;
/// <summary>
/// The maximum padded two-dimensional coefficient-context allocation size.
/// </summary>
public const int TransformPad2d = ((MaxTransformSize + TransformPadHorizontal) * (MaxTransformSize + TransformPadVertical)) + TransformPadEnd; public const int TransformPad2d = ((MaxTransformSize + TransformPadHorizontal) * (MaxTransformSize + TransformPadVertical)) + TransformPadEnd;
/// <summary>
/// The coefficient-context padding above a transform.
/// </summary>
public const int TransformPadTop = 2; public const int TransformPadTop = 2;
/// <summary>
/// The coefficient-context padding below a transform.
/// </summary>
public const int TransformPadBottom = 4; public const int TransformPadBottom = 4;
/// <summary>
/// The largest symbol in a coefficient base-range distribution.
/// </summary>
public const int BaseRangeSizeMinus1 = 3; public const int BaseRangeSizeMinus1 = 3;
/// <summary>
/// The largest coefficient magnitude represented before Golomb coding.
/// </summary>
public const int MaxBaseRange = 15; public const int MaxBaseRange = 15;
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public const int ChromaFromLumaAlphabetSizeLog2 = 4; public const int ChromaFromLumaAlphabetSizeLog2 = 4;
/// <summary> /// <summary>
/// Total number of Quantification Matrices sets stored. /// The number of quantization-matrix levels.
/// </summary> /// </summary>
public const int QuantificationMatrixLevelCount = 1 << 4; public const int QuantificationMatrixLevelCount = 1 << 4;
/// <summary>
/// The fixed-point precision of each quantization-matrix element.
/// </summary>
public const int QuantizationMatrixElementBitCount = 5; public const int QuantizationMatrixElementBitCount = 5;
/// <summary>
/// The directional intra-prediction angle increment in degrees.
/// </summary>
public const int AngleStep = 3; public const int AngleStep = 3;
/// <summary> /// <summary>
/// Maximum number of stages in a 1-dimensioanl transform function. /// The maximum number of stages in a one-dimensional transform function.
/// </summary> /// </summary>
public const int MaxTransformStageNumber = 12; public const int MaxTransformStageNumber = 12;
/// <summary>
/// The number of partition contexts per block-size logarithm.
/// </summary>
public const int PartitionProbabilitySet = 4; public const int PartitionProbabilitySet = 4;
// Number of transform sizes that use extended transforms. /// <summary>
/// The number of square transform-size contexts that can signal extended transforms.
/// </summary>
public const int ExtendedTransformCount = 4; public const int ExtendedTransformCount = 4;
/// <summary>
/// The highest variable-transform depth index.
/// </summary>
public const int MaxVarTransform = 2; public const int MaxVarTransform = 2;
/// <summary> /// <summary>
/// Maximum number of transform blocks per depth /// The maximum number of transform blocks at one depth.
/// </summary> /// </summary>
public const int MaxTransformBlockCount = 16; public const int MaxTransformBlockCount = 16;
@ -203,5 +286,8 @@ internal static class Av1Constants
/// </summary> /// </summary>
public const int PlaneTypeCount = 2; public const int PlaneTypeCount = 2;
/// <summary>
/// The maximum number of transform units stored for one encoded block.
/// </summary>
public const int MaxTransformUnitCount = 16; public const int MaxTransformUnitCount = 16;
} }

42
src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs

@ -9,24 +9,57 @@ using SixLabors.ImageSharp.PixelFormats.Utils;
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Decodes one AV1 still-image elementary stream into an ImageSharp image.
/// </summary>
internal class Av1Decoder : IAv1TileReader internal class Av1Decoder : IAv1TileReader
{ {
/// <summary>
/// The open-bitstream-unit parser for the current image item.
/// </summary>
private readonly ObuReader obuReader; private readonly ObuReader obuReader;
/// <summary>
/// The configuration used for decoded image and scratch-memory allocation.
/// </summary>
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary>
/// The tile parser shared by all tile groups in the current frame.
/// </summary>
private Av1TileReader? tileReader; private Av1TileReader? tileReader;
/// <summary>
/// Initializes a new instance of the <see cref="Av1Decoder"/> class.
/// </summary>
/// <param name="configuration">The configuration used for image and scratch-memory allocation.</param>
public Av1Decoder(Configuration configuration) public Av1Decoder(Configuration configuration)
{ {
this.configuration = configuration; this.configuration = configuration;
this.obuReader = new(); this.obuReader = new();
} }
/// <summary>
/// Gets the decoded frame header, or <see langword="null"/> before the stream provides one.
/// </summary>
public ObuFrameHeader? FrameHeader { get; private set; } public ObuFrameHeader? FrameHeader { get; private set; }
/// <summary>
/// Gets the decoded sequence header, or <see langword="null"/> before the stream provides one.
/// </summary>
public ObuSequenceHeader? SequenceHeader { get; private set; } public ObuSequenceHeader? SequenceHeader { get; private set; }
/// <summary>
/// Gets the tile and superblock state for the decoded frame, or <see langword="null"/> before tile parsing completes.
/// </summary>
public Av1FrameInfo? FrameInfo { get; private set; } public Av1FrameInfo? FrameInfo { get; private set; }
/// <summary>
/// Decodes an AV1 still-image elementary stream.
/// </summary>
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
/// <param name="buffer">The complete AV1 elementary-stream payload.</param>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Span<byte> buffer) public Image<TPixel> Decode<TPixel>(Span<byte> buffer)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -66,14 +99,21 @@ internal class Av1Decoder : IAv1TileReader
} }
} }
/// <summary>
/// Parses one entropy-coded tile payload into the current frame state.
/// </summary>
/// <param name="tileData">The entropy-coded tile payload.</param>
/// <param name="tileNum">The raster-order tile index.</param>
public void ReadTile(Span<byte> tileData, int tileNum) public void ReadTile(Span<byte> tileData, int tileNum)
{ {
if (this.tileReader == null) if (this.tileReader is null)
{ {
this.SequenceHeader = this.obuReader.SequenceHeader; this.SequenceHeader = this.obuReader.SequenceHeader;
this.FrameHeader = this.obuReader.FrameHeader; this.FrameHeader = this.obuReader.FrameHeader;
Guard.NotNull(this.SequenceHeader, nameof(this.SequenceHeader)); Guard.NotNull(this.SequenceHeader, nameof(this.SequenceHeader));
Guard.NotNull(this.FrameHeader, nameof(this.FrameHeader)); 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); this.tileReader = new Av1TileReader(this.configuration, this.SequenceHeader, this.FrameHeader);
} }

152
src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs

@ -9,19 +9,54 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary> /// <summary>
/// Buffer for the pixels of a single frame. /// Owns the padded luma and chroma sample planes for one decoded AV1 frame.
/// </summary> /// </summary>
/// <typeparam name="T">The unmanaged storage-element type used by the plane allocations.</typeparam>
internal class Av1FrameBuffer<T> : IDisposable internal class Av1FrameBuffer<T> : IDisposable
where T : unmanaged where T : unmanaged
{ {
/// <summary>
/// The number of border samples reserved for intra prediction and in-loop filtering.
/// </summary>
private const int DecoderPaddingValue = 72; private const int DecoderPaddingValue = 72;
/// <summary>
/// The allocation-mask bit for the luma plane.
/// </summary>
private const int PictureBufferYFlag = 1 << 0; private const int PictureBufferYFlag = 1 << 0;
/// <summary>
/// The allocation-mask bit for the first chroma plane.
/// </summary>
private const int PictureBufferCbFlag = 1 << 1; private const int PictureBufferCbFlag = 1 << 1;
/// <summary>
/// The allocation-mask bit for the second chroma plane.
/// </summary>
private const int PictureBufferCrFlag = 1 << 2; private const int PictureBufferCrFlag = 1 << 2;
/// <summary>
/// The allocation mask for a monochrome frame.
/// </summary>
private const int PictureBufferLumaMask = PictureBufferYFlag; private const int PictureBufferLumaMask = PictureBufferYFlag;
/// <summary>
/// The allocation mask for a frame containing all three planes.
/// </summary>
private const int PictureBufferFullMask = PictureBufferYFlag | PictureBufferCbFlag | PictureBufferCrFlag; private const int PictureBufferFullMask = PictureBufferYFlag | PictureBufferCbFlag | PictureBufferCrFlag;
/// <summary>
/// The number of <typeparamref name="T"/> elements occupied by one logical sample.
/// </summary>
private readonly int storageElementsPerSample; private readonly int storageElementsPerSample;
/// <summary>
/// Initializes a new instance of the <see cref="Av1FrameBuffer{T}"/> class.
/// </summary>
/// <param name="configuration">The configuration providing the plane allocator.</param>
/// <param name="sequenceHeader">The sequence header defining maximum dimensions, bit depth, and chroma layout.</param>
/// <param name="maxColorFormat">The maximum color format to allocate for a non-monochrome sequence.</param>
/// <param name="is16BitPipeline">Indicates whether reconstruction uses native 16-bit sample storage.</param>
public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHeader, Av1ColorFormat maxColorFormat, bool is16BitPipeline) public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHeader, Av1ColorFormat maxColorFormat, bool is16BitPipeline)
{ {
Av1ColorFormat colorFormat = sequenceHeader.ColorConfig.IsMonochrome ? Av1ColorFormat.Yuv400 : maxColorFormat; Av1ColorFormat colorFormat = sequenceHeader.ColorConfig.IsMonochrome ? Av1ColorFormat.Yuv400 : maxColorFormat;
@ -36,7 +71,7 @@ internal class Av1FrameBuffer<T> : IDisposable
this.ColorFormat = colorFormat; this.ColorFormat = colorFormat;
this.Is16BitPipeline = is16BitPipeline; this.Is16BitPipeline = is16BitPipeline;
this.BufferEnableMask = sequenceHeader.ColorConfig.IsMonochrome ? PictureBufferLumaMask : PictureBufferFullMask; int bufferEnableMask = sequenceHeader.ColorConfig.IsMonochrome ? PictureBufferLumaMask : PictureBufferFullMask;
int leftPadding = DecoderPaddingValue; int leftPadding = DecoderPaddingValue;
int rightPadding = DecoderPaddingValue; int rightPadding = DecoderPaddingValue;
@ -51,7 +86,6 @@ internal class Av1FrameBuffer<T> : IDisposable
int heightY = this.MaxHeight + topPadding + bottomPadding; int heightY = this.MaxHeight + topPadding + bottomPadding;
this.OriginX = leftPadding; this.OriginX = leftPadding;
this.OriginY = topPadding; this.OriginY = topPadding;
this.OriginOriginY = bottomPadding;
int strideChroma = 0; int strideChroma = 0;
int heightChroma = 0; int heightChroma = 0;
switch (this.ColorFormat) switch (this.ColorFormat)
@ -70,34 +104,28 @@ internal class Av1FrameBuffer<T> : IDisposable
break; break;
} }
this.PackedFlag = false;
this.BufferY = null; this.BufferY = null;
this.BufferCb = null; this.BufferCb = null;
this.BufferCr = null; this.BufferCr = null;
if ((this.BufferEnableMask & PictureBufferYFlag) != 0) if ((bufferEnableMask & PictureBufferYFlag) != 0)
{ {
this.BufferY = configuration.MemoryAllocator.Allocate2D<T>(strideY * this.storageElementsPerSample, heightY); this.BufferY = configuration.MemoryAllocator.Allocate2D<T>(strideY * this.storageElementsPerSample, heightY);
} }
if ((this.BufferEnableMask & PictureBufferCbFlag) != 0) if ((bufferEnableMask & PictureBufferCbFlag) != 0)
{ {
this.BufferCb = configuration.MemoryAllocator.Allocate2D<T>(strideChroma * this.storageElementsPerSample, heightChroma); this.BufferCb = configuration.MemoryAllocator.Allocate2D<T>(strideChroma * this.storageElementsPerSample, heightChroma);
} }
if ((this.BufferEnableMask & PictureBufferCrFlag) != 0) if ((bufferEnableMask & PictureBufferCrFlag) != 0)
{ {
this.BufferCr = configuration.MemoryAllocator.Allocate2D<T>(strideChroma * this.storageElementsPerSample, heightChroma); this.BufferCr = configuration.MemoryAllocator.Allocate2D<T>(strideChroma * this.storageElementsPerSample, heightChroma);
} }
this.BitIncrementY = null;
this.BitIncrementCb = null;
this.BitIncrementCr = null;
this.BitIncrementY = null;
this.BitIncrementCb = null;
this.BitIncrementCr = null;
} }
/// <summary>
/// Gets the padded luma-coordinate origin of the visible frame.
/// </summary>
public Point StartPosition { get; private set; } public Point StartPosition { get; private set; }
/// <summary> /// <summary>
@ -115,12 +143,6 @@ internal class Av1FrameBuffer<T> : IDisposable
/// </summary> /// </summary>
public Buffer2D<T>? BufferCr { get; private set; } public Buffer2D<T>? BufferCr { get; private set; }
public Buffer2D<byte>? BitIncrementY { get; private set; }
public Buffer2D<byte>? BitIncrementCb { get; private set; }
public Buffer2D<byte>? BitIncrementCr { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the horizontal padding distance. /// Gets or sets the horizontal padding distance.
/// </summary> /// </summary>
@ -132,22 +154,17 @@ internal class Av1FrameBuffer<T> : IDisposable
public int OriginY { get; set; } public int OriginY { get; set; }
/// <summary> /// <summary>
/// Gets or sets the vertical bottom padding distance /// Gets or sets the luma picture width, excluding padding.
/// </summary>
public int OriginOriginY { get; set; }
/// <summary>
/// Gets or sets the Luma picture width, which excludes the padding.
/// </summary> /// </summary>
public int Width { get; set; } public int Width { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Luma picture height, which excludes the padding. /// Gets or sets the luma picture height, excluding padding.
/// </summary> /// </summary>
public int Height { get; set; } public int Height { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Lume picture width. /// Gets or sets the maximum luma picture width.
/// </summary> /// </summary>
public int MaxWidth { get; set; } public int MaxWidth { get; set; }
@ -167,33 +184,23 @@ internal class Av1FrameBuffer<T> : IDisposable
public ObuColorConfig ColorConfig { get; } public ObuColorConfig ColorConfig { get; }
/// <summary> /// <summary>
/// Gets or sets the chroma subsampling. /// Gets or sets the luma and chroma plane sampling layout.
/// </summary> /// </summary>
public Av1ColorFormat ColorFormat { get; set; } public Av1ColorFormat ColorFormat { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Luma picture height. /// Gets or sets the maximum luma picture height.
/// </summary> /// </summary>
public int MaxHeight { get; set; } public int MaxHeight { get; set; }
public int LumaSize { get; }
public int ChromaSize { get; }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public bool PackedFlag { get; set; } public bool Is16BitPipeline { get; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether film grain parameters are present for this frame. /// Releases the owned luma and chroma plane allocations.
/// </summary> /// </summary>
public bool FilmGrainFlag { get; set; }
public int BufferEnableMask { get; set; }
public bool Is16BitPipeline { get; set; }
public void Dispose() public void Dispose()
{ {
this.BufferY?.Dispose(); this.BufferY?.Dispose();
@ -202,20 +209,17 @@ internal class Av1FrameBuffer<T> : IDisposable
this.BufferCb = null; this.BufferCb = null;
this.BufferCr?.Dispose(); this.BufferCr?.Dispose();
this.BufferCr = null; this.BufferCr = null;
this.BitIncrementY?.Dispose();
this.BitIncrementY = null;
this.BitIncrementCb?.Dispose();
this.BitIncrementCb = null;
this.BitIncrementCr?.Dispose();
this.BitIncrementCr = null;
} }
/// <summary> /// <summary>
/// Returns a <see cref="Span{T}"/> starting at 1 row before this blocks pixels. /// Gets a storage-element span beginning one logical row before a block.
/// </summary> /// </summary>
/// <remarks> /// <param name="plane">The luma or chroma plane.</param>
/// SVT: svt_aom_derive_blk_pointers /// <param name="locationInPixels">The block origin in plane samples.</param>
/// </remarks> /// <param name="subX">The horizontal chroma subsampling shift.</param>
/// <param name="subY">The vertical chroma subsampling shift.</param>
/// <param name="stride">Receives the logical samples between adjacent rows.</param>
/// <returns>The span beginning one logical row before the block.</returns>
public Span<T> DeriveBlockPointer(Av1Plane plane, Point locationInPixels, int subX, int subY, out int stride) public Span<T> DeriveBlockPointer(Av1Plane plane, Point locationInPixels, int subX, int subY, out int stride)
{ {
this.GetPlaneLayout( this.GetPlaneLayout(
@ -233,7 +237,7 @@ internal class Av1FrameBuffer<T> : IDisposable
int blockOffset = (((originY + locationInPixels.Y) * stride) + originX + locationInPixels.X) * int blockOffset = (((originY + locationInPixels.Y) * stride) + originX + locationInPixels.X) *
this.storageElementsPerSample; 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; blockOffset -= elementStride;
Guard.MustBeGreaterThanOrEqualTo(blockOffset, 0, nameof(blockOffset)); Guard.MustBeGreaterThanOrEqualTo(blockOffset, 0, nameof(blockOffset));
@ -241,11 +245,14 @@ internal class Av1FrameBuffer<T> : IDisposable
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <remarks> /// <param name="plane">The luma or chroma plane.</param>
/// SVT: svt_aom_derive_blk_pointers /// <param name="locationInPixels">The block origin in plane samples.</param>
/// </remarks> /// <param name="subX">The horizontal chroma subsampling shift.</param>
/// <param name="subY">The vertical chroma subsampling shift.</param>
/// <param name="stride">Receives the logical samples between adjacent rows.</param>
/// <returns>The 16-bit span beginning one logical row before the block.</returns>
public Span<short> DeriveBlockPointer16(Av1Plane plane, Point locationInPixels, int subX, int subY, out int stride) public Span<short> DeriveBlockPointer16(Av1Plane plane, Point locationInPixels, int subX, int subY, out int stride)
{ {
this.GetPlaneLayout( this.GetPlaneLayout(
@ -267,11 +274,12 @@ internal class Av1FrameBuffer<T> : IDisposable
} }
/// <summary> /// <summary>
/// Returns a <see cref="Buffer2DRegion{T}"/> starting at top left pixel of the block of the specified plane. /// Gets the visible sample region for one plane.
/// </summary> /// </summary>
/// <remarks> /// <param name="plane">The luma or chroma plane.</param>
/// SVT: svt_aom_derive_blk_pointers /// <param name="subX">The horizontal chroma subsampling shift.</param>
/// </remarks> /// <param name="subY">The vertical chroma subsampling shift.</param>
/// <returns>The plane region excluding decoder padding.</returns>
public Buffer2DRegion<T> DeriveBlockPointer(Av1Plane plane, int subX, int subY) public Buffer2DRegion<T> DeriveBlockPointer(Av1Plane plane, int subX, int subY)
{ {
this.GetPlaneLayout( this.GetPlaneLayout(
@ -294,8 +302,13 @@ internal class Av1FrameBuffer<T> : IDisposable
} }
/// <summary> /// <summary>
/// Returns one logical row of 16-bit samples from the specified plane. /// Gets one visible row of native 16-bit samples from a plane.
/// </summary> /// </summary>
/// <param name="plane">The luma or chroma plane.</param>
/// <param name="row">The zero-based visible row index.</param>
/// <param name="subX">The horizontal chroma subsampling shift.</param>
/// <param name="subY">The vertical chroma subsampling shift.</param>
/// <returns>The visible row without decoder padding.</returns>
public Span<ushort> GetHighBitDepthRowSpan(Av1Plane plane, int row, int subX, int subY) public Span<ushort> GetHighBitDepthRowSpan(Av1Plane plane, int row, int subX, int subY)
{ {
this.GetPlaneLayout( this.GetPlaneLayout(
@ -312,6 +325,17 @@ internal class Av1FrameBuffer<T> : IDisposable
return samples.Slice(originX, width); return samples.Slice(originX, width);
} }
/// <summary>
/// Resolves a plane allocation and its visible padded layout.
/// </summary>
/// <param name="plane">The luma or chroma plane.</param>
/// <param name="subX">The horizontal chroma subsampling shift.</param>
/// <param name="subY">The vertical chroma subsampling shift.</param>
/// <param name="buffer">Receives the selected plane allocation.</param>
/// <param name="originX">Receives the horizontal visible origin in plane samples.</param>
/// <param name="originY">Receives the vertical visible origin in plane samples.</param>
/// <param name="width">Receives the visible plane width.</param>
/// <param name="height">Receives the visible plane height.</param>
private void GetPlaneLayout( private void GetPlaneLayout(
Av1Plane plane, Av1Plane plane,
int subX, int subX,

152
src/ImageSharp/Formats/Heif/Av1/Av1Math.cs

@ -3,14 +3,22 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Provides the integer arithmetic primitives used by AV1 syntax and reconstruction.
/// </summary>
internal static class Av1Math internal static class Av1Math
{ {
/// <summary>
/// Gets the zero-based position of the most significant set bit.
/// </summary>
/// <param name="value">A nonzero unsigned value.</param>
/// <returns>The most significant set-bit position.</returns>
public static int MostSignificantBit(uint value) public static int MostSignificantBit(uint value)
{ {
int log = 0; int log = 0;
int i; 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) for (i = 4; i >= 0; --i)
{ {
@ -26,6 +34,11 @@ internal static class Av1Math
return log; return log;
} }
/// <summary>
/// Gets the integer base-two logarithm of a positive value.
/// </summary>
/// <param name="n">The value.</param>
/// <returns>The zero-based position of the most significant set bit.</returns>
public static int Log2(int n) public static int Log2(int n)
{ {
int result = 0; int result = 0;
@ -38,10 +51,10 @@ internal static class Av1Math
} }
/// <summary> /// <summary>
/// Long Log 2 /// Gets the integer base-two logarithm of an unsigned 32-bit value.
/// This is a quick adaptation of a Number
/// Leading Zeros(NLZ) algorithm to get the log2f of a 32-bit number
/// </summary> /// </summary>
/// <param name="x">The value.</param>
/// <returns>The zero-based position of the most significant set bit.</returns>
internal static uint Log2_32(uint x) internal static uint Log2_32(uint x)
{ {
uint log = 0; uint log = 0;
@ -60,6 +73,11 @@ internal static class Av1Math
return log; return log;
} }
/// <summary>
/// Gets the greatest integer less than or equal to the base-two logarithm of a nonzero value.
/// </summary>
/// <param name="value">The nonzero value.</param>
/// <returns>The floor of the base-two logarithm.</returns>
public static uint FloorLog2(uint value) public static uint FloorLog2(uint value)
{ {
uint s = 0; uint s = 0;
@ -72,6 +90,11 @@ internal static class Av1Math
return s - 1; return s - 1;
} }
/// <summary>
/// Gets the least integer greater than or equal to the base-two logarithm of a value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The ceiling of the base-two logarithm, or zero for values below two.</returns>
public static uint CeilLog2(uint value) public static uint CeilLog2(uint value)
{ {
if (value < 2) if (value < 2)
@ -90,13 +113,39 @@ internal static class Av1Math
return i; return i;
} }
/// <summary>
/// Clips an unsigned sample to the range represented by a bit depth.
/// </summary>
/// <param name="value">The sample value.</param>
/// <param name="bitDepth">The number of sample bits.</param>
/// <returns>The clipped sample.</returns>
public static uint Clip1(uint value, int bitDepth) => public static uint Clip1(uint value, int bitDepth) =>
Clip3(0, (1U << bitDepth) - 1, value); Clip3(0, (1U << bitDepth) - 1, value);
/// <summary>
/// Clips an unsigned value to an inclusive range.
/// </summary>
/// <param name="min">The inclusive lower bound.</param>
/// <param name="max">The inclusive upper bound.</param>
/// <param name="value">The value to clip.</param>
/// <returns>The clipped value.</returns>
public static uint Clip3(uint min, uint max, uint value) => Math.Max(min, Math.Min(max, value)); public static uint Clip3(uint min, uint max, uint value) => Math.Max(min, Math.Min(max, value));
/// <summary>
/// Clips a signed value to an inclusive range.
/// </summary>
/// <param name="min">The inclusive lower bound.</param>
/// <param name="max">The inclusive upper bound.</param>
/// <param name="value">The value to clip.</param>
/// <returns>The clipped value.</returns>
public static int Clip3(int min, int max, int value) => Math.Max(min, Math.Min(max, value)); public static int Clip3(int min, int max, int value) => Math.Max(min, Math.Min(max, value));
/// <summary>
/// Divides an unsigned value by a power of two with nearest-integer rounding.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="n">The base-two divisor exponent.</param>
/// <returns>The rounded quotient.</returns>
public static uint Round2(uint value, int n) public static uint Round2(uint value, int n)
{ {
if (n == 0) if (n == 0)
@ -107,6 +156,12 @@ internal static class Av1Math
return (uint)((value + (1 << (n - 1))) >> n); return (uint)((value + (1 << (n - 1))) >> n);
} }
/// <summary>
/// Divides the absolute magnitude of a signed value by a power of two with nearest-integer rounding.
/// </summary>
/// <param name="value">The signed value.</param>
/// <param name="n">The base-two divisor exponent.</param>
/// <returns>The rounded nonnegative magnitude.</returns>
public static int Round2(int value, int n) public static int Round2(int value, int n)
{ {
if (value < 0) if (value < 0)
@ -117,37 +172,102 @@ internal static class Av1Math
return (int)Round2((uint)value, n); return (int)Round2((uint)value, n);
} }
/// <summary>
/// Aligns a value upward to a multiple of a power of two.
/// </summary>
/// <param name="value">The value to align.</param>
/// <param name="n">The base-two alignment exponent.</param>
/// <returns>The aligned value.</returns>
internal static int AlignPowerOf2(int value, int n) internal static int AlignPowerOf2(int value, int n)
{ {
int mask = (1 << n) - 1; int mask = (1 << n) - 1;
return (value + mask) & ~mask; return (value + mask) & ~mask;
} }
/// <summary>
/// Divides a value by a power of two with nearest-integer rounding.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="n">The base-two divisor exponent.</param>
/// <returns>The rounded quotient.</returns>
internal static int RoundPowerOf2(int value, int n) => (value + ((1 << n) >> 1)) >> n; internal static int RoundPowerOf2(int value, int n) => (value + ((1 << n) >> 1)) >> n;
/// <summary>
/// Clamps a signed integer to an inclusive range.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="low">The inclusive lower bound.</param>
/// <param name="high">The inclusive upper bound.</param>
/// <returns>The clamped value.</returns>
internal static int Clamp(int value, int low, int high) internal static int Clamp(int value, int low, int high)
=> Math.Max(low, Math.Min(high, value)); => Math.Max(low, Math.Min(high, value));
/// <summary>
/// Clamps a signed long integer to an inclusive range.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="low">The inclusive lower bound.</param>
/// <param name="high">The inclusive upper bound.</param>
/// <returns>The clamped value.</returns>
internal static long Clamp(long value, long low, long high) internal static long Clamp(long value, long low, long high)
=> Math.Max(low, Math.Min(high, value)); => Math.Max(low, Math.Min(high, value));
/// <summary>
/// Divides a value by a power of two with floor rounding.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="n">The base-two divisor exponent.</param>
/// <returns>The floor-rounded quotient.</returns>
internal static int DivideLog2Floor(int value, int n) internal static int DivideLog2Floor(int value, int n)
=> value >> n; => value >> n;
/// <summary>
/// Divides a nonnegative value by a power of two with ceiling rounding.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="n">The base-two divisor exponent.</param>
/// <returns>The ceiling-rounded quotient.</returns>
internal static int DivideLog2Ceiling(int value, int n) internal static int DivideLog2Ceiling(int value, int n)
=> (value + (1 << n) - 1) >> n; => (value + (1 << n) - 1) >> n;
/// <summary>
/// Divides a value by a power of two with nearest-integer rounding.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="bitCount">The base-two divisor exponent.</param>
/// <returns>The rounded quotient.</returns>
internal static int DivideRound(int value, int bitCount) internal static int DivideRound(int value, int bitCount)
=> (value + (1 << (bitCount - 1))) >> bitCount; => (value + (1 << (bitCount - 1))) >> bitCount;
// Last 3 bits are the value of mod 8. /// <summary>
/// Gets the nonnegative remainder after division by eight.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The low three bits of the value.</returns>
internal static int Modulus8(int value) => value & 0x07; internal static int Modulus8(int value) => value & 0x07;
/// <summary>
/// Divides a value by eight with floor rounding.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The floor-rounded quotient.</returns>
internal static int DivideBy8Floor(int value) => value >> 3; internal static int DivideBy8Floor(int value) => value >> 3;
/// <summary>
/// Divides a signed value by a power of two with symmetric nearest-integer rounding.
/// </summary>
/// <param name="value">The signed value.</param>
/// <param name="n">The base-two divisor exponent.</param>
/// <returns>The signed rounded quotient.</returns>
internal static int RoundPowerOf2Signed(int value, int n) internal static int RoundPowerOf2Signed(int value, int n)
=> (value < 0) ? -RoundPowerOf2(-value, n) : RoundPowerOf2(value, n); => (value < 0) ? -RoundPowerOf2(-value, n) : RoundPowerOf2(value, n);
/// <summary>
/// Right-shifts a long intermediate with nearest-integer rounding.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="bit">The positive shift count.</param>
/// <returns>The rounded signed result.</returns>
internal static int RoundShift(long value, int bit) internal static int RoundShift(long value, int bit)
{ {
DebugGuard.MustBeGreaterThanOrEqualTo(bit, 1, nameof(bit)); DebugGuard.MustBeGreaterThanOrEqualTo(bit, 1, nameof(bit));
@ -155,15 +275,35 @@ internal static class Av1Math
} }
/// <summary> /// <summary>
/// <paramref name="a"/> implies <paramref name="b"/>. /// Evaluates logical implication from one Boolean condition to another.
/// </summary> /// </summary>
/// <param name="a">The antecedent.</param>
/// <param name="b">The consequent.</param>
/// <returns><see langword="false"/> only when <paramref name="a"/> is true and <paramref name="b"/> is false.</returns>
internal static bool Implies(bool a, bool b) => !a || b; internal static bool Implies(bool a, bool b) => !a || b;
/// <summary>
/// Gets one bit from an integer value.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="n">The zero-based bit position.</param>
/// <returns>Zero or one.</returns>
internal static int GetBit(int value, int n) internal static int GetBit(int value, int n)
=> (value & (1 << n)) >> n; => (value & (1 << n)) >> n;
/// <summary>
/// Sets one bit in an integer value.
/// </summary>
/// <param name="endOfBlockExtra">The value to update.</param>
/// <param name="n">The zero-based bit position.</param>
internal static void SetBit(ref int endOfBlockExtra, int n) internal static void SetBit(ref int endOfBlockExtra, int n)
=> endOfBlockExtra |= 1 << n; => endOfBlockExtra |= 1 << n;
/// <summary>
/// Gets the absolute difference between two integers.
/// </summary>
/// <param name="a">The first value.</param>
/// <param name="b">The second value.</param>
/// <returns>The nonnegative absolute difference.</returns>
internal static int AbsoluteDifference(int a, int b) => (a > b) ? a - b : b - a; internal static int AbsoluteDifference(int a, int b) => (a > b) ? a - b : b - a;
} }

5
src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs

@ -3,10 +3,11 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Identifies the subdivision pattern applied to an AV1 coding block.
/// </summary>
internal enum Av1PartitionType internal enum Av1PartitionType
{ {
// See section 6.10.4 of Avi Spcification
/// <summary> /// <summary>
/// Not partitioned any further. /// Not partitioned any further.
/// </summary> /// </summary>

12
src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs

@ -3,8 +3,14 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Provides child-block geometry for AV1 partition types.
/// </summary>
internal static class Av1PartitionTypeExtensions internal static class Av1PartitionTypeExtensions
{ {
/// <summary>
/// Maps each partition type and parent block size to the size of its component blocks.
/// </summary>
private static readonly Av1BlockSize[][] PartitionSubSize = [ private static readonly Av1BlockSize[][] PartitionSubSize = [
[ [
Av1BlockSize.Block4x4, Av1BlockSize.Block4x4,
@ -99,6 +105,12 @@ internal static class Av1PartitionTypeExtensions
] ]
]; ];
/// <summary>
/// Gets the component block size produced by a partition operation.
/// </summary>
/// <param name="partition">The partition operation.</param>
/// <param name="blockSize">The parent block size.</param>
/// <returns>The component block size, or <see cref="Av1BlockSize.Invalid"/> when the partition is not permitted.</returns>
public static Av1BlockSize GetBlockSubSize(this Av1PartitionType partition, Av1BlockSize blockSize) public static Av1BlockSize GetBlockSubSize(this Av1PartitionType partition, Av1BlockSize blockSize)
=> PartitionSubSize[(int)partition][(int)blockSize]; => PartitionSubSize[(int)partition][(int)blockSize];
} }

14
src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs

@ -3,9 +3,23 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Identifies an AV1 luma or chroma sample plane.
/// </summary>
internal enum Av1Plane : int internal enum Av1Plane : int
{ {
/// <summary>
/// The luma plane.
/// </summary>
Y = 0, Y = 0,
/// <summary>
/// The first chroma plane.
/// </summary>
U = 1, U = 1,
/// <summary>
/// The second chroma plane.
/// </summary>
V = 2, V = 2,
} }

20
src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs

@ -10,14 +10,34 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Converts between reconstructed AV1 YUV planes and packed ImageSharp pixels.
/// </summary>
internal static class Av1YuvConverter internal static class Av1YuvConverter
{ {
/// <summary>
/// The largest value represented by an eight-bit packed RGB component.
/// </summary>
private const float ByteMaximum = byte.MaxValue; private const float ByteMaximum = byte.MaxValue;
/// <summary>
/// Identifies the matrix operation used between encoded planes and RGB components.
/// </summary>
private enum ConversionMode private enum ConversionMode
{ {
/// <summary>
/// A coefficient-based YCbCr matrix conversion.
/// </summary>
Coefficients, Coefficients,
/// <summary>
/// Direct G, B, and R component mapping from the Y, U, and V planes.
/// </summary>
Identity, Identity,
/// <summary>
/// The reversible-style YCgCo color transform.
/// </summary>
YCgCo, YCgCo,
} }

2
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs

@ -1792,7 +1792,7 @@ internal static class Av1DefaultDistributions
]; ];
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
private static Av1Distribution[][][][] EndOfBlockExtra => private static Av1Distribution[][][][] EndOfBlockExtra =>
[ [

7
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs

@ -612,8 +612,9 @@ internal ref struct Av1SymbolDecoder
int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint]; int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint];
if (endOfBlockShift > 0) if (endOfBlockShift > 0)
{ {
// Extra-bit distributions start with token three because the first three tokens have no extra bits. // The local table retains placeholders for the first three tokens, unlike libaom's compact table,
int endOfBlockContext = endOfBlockPoint - 3; // so the decoded token is also the distribution index.
int endOfBlockContext = endOfBlockPoint;
bool bit = this.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext); bool bit = this.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext);
if (bit) if (bit)
{ {
@ -788,7 +789,7 @@ internal ref struct Av1SymbolDecoder
/// </summary> /// </summary>
/// <param name="transformSizeContext">The square transform-size probability context.</param> /// <param name="transformSizeContext">The square transform-size probability context.</param>
/// <param name="planeType">The luma or chroma plane category.</param> /// <param name="planeType">The luma or chroma plane category.</param>
/// <param name="endOfBlockContext">The zero-based extra-bit token context.</param> /// <param name="endOfBlockContext">The token-aligned extra-bit context in the padded local table.</param>
/// <returns>The decoded suffix bit.</returns> /// <returns>The decoded suffix bit.</returns>
private bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext) private bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext)
{ {

5
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs

@ -355,7 +355,10 @@ internal class Av1SymbolEncoder : IDisposable
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
int eobShift = eobOffsetBitCount - 1; int eobShift = eobOffsetBitCount - 1;
int bit = Av1Math.GetBit(eobExtra, eobShift); 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]); w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockContext]);
for (int i = 1; i < eobOffsetBitCount; i++) for (int i = 1; i < eobOffsetBitCount; i++)
{ {

2
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs

@ -989,7 +989,7 @@ internal class ObuReader
} }
else else
{ {
frameHeader.PrimaryReferenceFrame = reader.ReadLiteral(Av1Constants.PimaryReferenceBits); frameHeader.PrimaryReferenceFrame = reader.ReadLiteral(Av1Constants.PrimaryReferenceBits);
} }
// Skipping, as no decoder info model present // Skipping, as no decoder info model present

4
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs

@ -68,7 +68,9 @@ internal class ObuWriter
private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span<byte> payload) private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span<byte> payload)
{ {
stream.WriteByte(WriteObuHeader(type)); stream.WriteByte(WriteObuHeader(type));
Span<byte> lengthBytes = stackalloc byte[3];
// A 32-bit OBU payload length requires at most five base-128 bytes.
Span<byte> lengthBytes = stackalloc byte[5];
int lengthLength = Av1BitStreamWriter.GetLittleEndianBytes128((uint)payload.Length, lengthBytes); int lengthLength = Av1BitStreamWriter.GetLittleEndianBytes128((uint)payload.Length, lengthBytes);
stream.Write(lengthBytes, 0, lengthLength); stream.Write(lengthBytes, 0, lengthLength);
stream.Write(payload); stream.Write(payload);

Loading…
Cancel
Save