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;
/// <summary>
/// Identifies the sample precision of an AV1 sequence.
/// </summary>
internal enum Av1BitDepth : int
{
/// <summary>
/// Eight bits per sample.
/// </summary>
EightBit = 0,
/// <summary>
/// Ten bits per sample.
/// </summary>
TenBit = 1,
/// <summary>
/// Twelve bits per sample.
/// </summary>
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;
/// <summary>
/// Provides sample-precision conversions for AV1 bit-depth values.
/// </summary>
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);
}

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

@ -3,23 +3,48 @@
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
{
/// <summary>
/// The complete encoded byte span.
/// </summary>
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;
/// <summary>
/// Gets the zero-based position of the next bit to read.
/// </summary>
public int BitPosition { get; private set; } = 0;
/// <summary>
/// Gets the number of bytes in the readers buffer.
/// Gets the number of bytes in the reader's buffer.
/// </summary>
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;
/// <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;
/// <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)
{
DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount));
@ -33,6 +58,10 @@ internal ref struct Av1BitStreamReader
return literal;
}
/// <summary>
/// Reads the next encoded bit.
/// </summary>
/// <returns>Zero or one.</returns>
internal uint ReadBit()
{
int byteOffset = Av1Math.DivideBy8Floor(this.BitPosition);
@ -41,11 +70,19 @@ internal ref struct Av1BitStreamReader
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;
/// <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)
{
// 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;
}
/// <summary>
/// Reads the AV1 unsigned-variable-length code.
/// </summary>
/// <returns>The decoded unsigned value.</returns>
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;
}
/// <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)
{
// 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);
}
/// <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)
{
// 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;
}
/// <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)
{
// 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;
}
/// <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)
{
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;
/// <summary>
/// Writes AV1 fixed-width and variable-length syntax to reusable expanding memory.
/// </summary>
internal ref struct Av1BitStreamWriter
{
/// <summary>
/// The number of bits in one output byte.
/// </summary>
private const int WordSize = 8;
/// <summary>
/// The expanding output allocation.
/// </summary>
private readonly AutoExpandingMemory<byte> memory;
/// <summary>
/// The current writable view over <see cref="memory"/>.
/// </summary>
private Span<byte> span;
/// <summary>
/// The final byte index that can be written without expanding <see cref="memory"/>.
/// </summary>
private int capacityTrigger;
/// <summary>
/// The partially assembled output byte.
/// </summary>
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)
{
this.memory = memory;
@ -20,36 +46,45 @@ internal ref struct Av1BitStreamWriter
this.capacityTrigger = memory.Capacity - 1;
}
/// <summary>
/// Gets the zero-based position of the next output bit.
/// </summary>
public int BitPosition { get; private set; } = 0;
/// <summary>
/// Gets the current output capacity in bytes.
/// </summary>
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)
{
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;
}
/// <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)
{
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()
{
if (Av1Math.Modulus8(this.BitPosition) != 0)
@ -71,6 +109,11 @@ internal ref struct Av1BitStreamWriter
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)
{
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)
{
byte boolByte = value ? (byte)1 : (byte)0;
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)
{
// 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);
}
/// <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)
{
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;
}
/// <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)
{
// See section 4.10.7 of the AV1-Specification
if (numberOfSymbols <= 1)
{
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)
{
int bit = this.BitPosition & 0x07;
@ -138,9 +210,13 @@ internal ref struct Av1BitStreamWriter
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)
{
// 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
}
}
/// <summary>
/// Writes a byte-aligned entropy-coded tile payload.
/// </summary>
/// <param name="tileData">The tile payload.</param>
internal void WriteBlob(Span<byte> 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;
}
/// <summary>
/// Stores the current output byte, expanding the allocation when necessary.
/// </summary>
private void WriteBuffer()
{
int wordPosition = Av1Math.DivideBy8Floor(this.BitPosition);

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

@ -3,10 +3,11 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Identifies every luma block size defined by AV1.
/// </summary>
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>
Block4x4 = 0,
@ -72,8 +73,24 @@ internal enum Av1BlockSize : byte
/// <summary>A block of samples, 64 samples wide and 16 samples high.</summary>
Block64x16 = 21,
/// <summary>
/// The number of concrete block-size values.
/// </summary>
AllSizes = 22,
/// <summary>
/// The first extended rectangular block size following the primary size set.
/// </summary>
SizeS = Block4x16,
/// <summary>
/// A sentinel representing an invalid block size.
/// </summary>
Invalid = 255,
/// <summary>
/// The final value in the primary block-size set.
/// </summary>
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;
/// <summary>
/// Provides dimensions, chroma subsampling, and transform limits for AV1 block sizes.
/// </summary>
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];
/// <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];
// 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 =
[
@ -40,6 +52,9 @@ internal static class Av1BlockSizeExtensions
[[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 = [
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
];
/// <summary>
/// Contains the base-two logarithm of the sample count for each block size.
/// </summary>
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];
/// <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 = [
[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]
];
/// <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];
/// <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];
/// <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>
/// <param name="widthLog2">Log2 of the width value.</param>
/// <param name="heightLog2">Log2 of the height value.</param>
/// <returns>The <see cref="Av1BlockSize"/>.</returns>
public static Av1BlockSize FromWidthAndHeight(uint widthLog2, uint heightLog2) => HeightWidthToSize[heightLog2][widthLog2];
/// <param name="widthLog2">The base-two width logarithm minus two.</param>
/// <param name="heightLog2">The base-two height logarithm minus two.</param>
/// <returns>The matching block size, or <see cref="Av1BlockSize.Invalid"/> for unsupported dimensions.</returns>
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>
/// Returns the width of the block in samples.
/// Gets the block width in samples.
/// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The block width in samples.</returns>
public static int GetWidth(this Av1BlockSize blockSize)
=> Get4x4WideCount(blockSize) << 2;
/// <summary>
/// Returns of the height of the block in 4 samples.
/// Gets the block height in samples.
/// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The block height in samples.</returns>
public static int GetHeight(this Av1BlockSize blockSize)
=> Get4x4HighCount(blockSize) << 2;
/// <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>
/// <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)
=> Av1Math.Log2(Get4x4WideCount(blockSize));
/// <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>
/// <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)
=> Av1Math.Log2(Get4x4HighCount(blockSize));
/// <summary>
/// Returns the block size of a sub sampled block.
/// Gets the residual-plane block size for Boolean chroma subsampling flags.
/// </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)
=> GetSubsampled(blockSize, subX ? 1 : 0, subY ? 1 : 0);
/// <summary>
/// Returns the block size of a sub sampled block.
/// Gets the residual-plane block size for chroma subsampling shifts.
/// </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)
{
if (blockSize == Av1BlockSize.Invalid)
@ -116,6 +167,13 @@ internal static class Av1BlockSizeExtensions
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)
{
Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY);
@ -135,12 +193,18 @@ internal static class Av1BlockSizeExtensions
}
/// <summary>
/// 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.
/// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The maximum transform size.</returns>
public static Av1TransformSize GetMaximumTransformSize(this Av1BlockSize 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)
=> PelsLog2Count[(int)blockSize];
}

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

@ -3,11 +3,14 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// 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.
/// </summary>
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)
{
Av1BitStreamReader reader = new(boxBuffer);
@ -35,29 +38,68 @@ internal struct Av1CodecConfiguration
}
}
/// <summary>
/// Gets the one-bit configuration marker.
/// </summary>
public byte Marker { get; }
/// <summary>
/// Gets the codec-configuration record version.
/// </summary>
public byte Version { get; }
/// <summary>
/// Gets the sequence profile declared by the configuration record.
/// </summary>
public byte SeqProfile { get; }
/// <summary>
/// Gets the first operating point's sequence level index.
/// </summary>
public byte SeqLevelIdx0 { get; }
/// <summary>
/// Gets the first operating point's sequence tier flag.
/// </summary>
public byte SeqTier0 { get; }
/// <summary>
/// Gets the high-bit-depth flag.
/// </summary>
public byte HighBitdepth { get; }
/// <summary>
/// Gets a value indicating whether the sequence uses twelve-bit samples.
/// </summary>
public bool TwelveBit { get; }
/// <summary>
/// Gets a value indicating whether the sequence contains only a luma plane.
/// </summary>
public bool MonoChrome { get; }
/// <summary>
/// Gets a value indicating whether chroma is horizontally subsampled.
/// </summary>
public bool ChromaSubsamplingX { get; }
/// <summary>
/// Gets a value indicating whether chroma is vertically subsampled.
/// </summary>
public bool ChromaSubsamplingY { get; }
/// <summary>
/// Gets the chroma sample-position code.
/// </summary>
public byte ChromaSamplePosition { get; }
/// <summary>
/// Gets a value indicating whether an initial presentation delay is declared.
/// </summary>
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; }
}

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

@ -3,10 +3,28 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Identifies the AV1 luma and chroma plane sampling layout.
/// </summary>
internal enum Av1ColorFormat
{
/// <summary>
/// Monochrome luma samples without chroma planes.
/// </summary>
Yuv400,
/// <summary>
/// Chroma samples subsampled by two horizontally and vertically.
/// </summary>
Yuv420,
/// <summary>
/// Chroma samples subsampled by two horizontally.
/// </summary>
Yuv422,
/// <summary>
/// Full-resolution luma and chroma samples.
/// </summary>
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;
/// <summary>
/// Defines shared AV1 syntax, geometry, entropy, and transform limits.
/// </summary>
internal static class Av1Constants
{
/// <summary>
/// The highest sequence profile defined by AV1.
/// </summary>
public const ObuSequenceProfile MaxSequenceProfile = ObuSequenceProfile.Professional;
/// <summary>
/// The number of bits used for an operating-point level index.
/// </summary>
public const int LevelBits = 5;
/// <summary>
/// Number of fractional bits for computing position in upscaling.
/// The number of bits used to signal a super-resolution denominator offset.
/// </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>
/// Number of reference frames that can be used for inter prediction.
/// The number of reference frames that can be used for inter prediction.
/// </summary>
public const int ReferencesPerFrame = 7;
/// <summary>
/// Maximum area of a tile in units of luma samples.
/// The maximum area of a tile in units of luma samples.
/// </summary>
public const int MaxTileArea = 4096 * 2304;
/// <summary>
/// Maximum width of a tile in units of luma samples.
/// The maximum width of a tile in units of luma samples.
/// </summary>
public const int MaxTileWidth = 4096;
/// <summary>
/// Maximum number of tile columns.
/// The maximum number of tile columns.
/// </summary>
public const int MaxTileColumnCount = 64;
/// <summary>
/// Maximum number of tile rows.
/// The maximum number of tile rows.
/// </summary>
public const int MaxTileRowCount = 64;
/// <summary>
/// Number of frames that can be stored for future reference.
/// The number of frames that can be stored for future reference.
/// </summary>
public const int ReferenceFrameCount = 8;
/// <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>
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>
/// Number of segments allowed in segmentation map.
/// The number of segments allowed in a segmentation map.
/// </summary>
public const int MaxSegmentCount = 8;
/// <summary>
/// Smallest denominator for upscaling ratio.
/// The smallest signaled denominator for an active super-resolution ratio.
/// </summary>
public const int SuperResolutionScaleDenominatorMinimum = 9;
/// <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>
public const int MaxSuperBlockSizeLog2 = 7;
/// <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>
public const int ModeInfoSizeLog2 = 2;
/// <summary>
/// The maximum quantizer index.
/// </summary>
public const int MaxQ = 255;
/// <summary>
/// Number of segmentation features.
/// The number of segmentation features.
/// </summary>
public const int SegmentationLevelMax = 8;
/// <summary>
/// Maximum size of a loop restoration tile.
/// The maximum loop-restoration tile size in samples.
/// </summary>
public const int RestorationMaxTileSize = 256;
/// <summary>
/// Number of Wiener coefficients to read.
/// The number of independent Wiener filter coefficients per direction.
/// </summary>
public const int WienerCoefficientCount = 3;
/// <summary>
/// The number of luma and chroma frame loop-filter levels.
/// </summary>
public const int FrameLoopFilterCount = 4;
/// <summary>
/// Value indicating alternative encoding of quantizer index delta values.
/// The first quantizer-delta magnitude encoded through the escape path.
/// </summary>
public const int DeltaQuantizerSmall = 3;
/// <summary>
/// Value indicating alternative encoding of loop filter delta values.
/// The first loop-filter-delta magnitude encoded through the escape path.
/// </summary>
public const int DeltaLoopFilterSmall = 3;
/// <summary>
/// Maximum value used for loop filtering.
/// The maximum loop-filter strength.
/// </summary>
public const int MaxLoopFilter = 63;
/// <summary>
/// Maximum magnitude of AngleDeltaY and AngleDeltaUV.
/// The maximum directional-prediction angle-delta magnitude.
/// </summary>
public const int MaxAngleDelta = 3;
/// <summary>
/// Maximum number of color planes.
/// The maximum number of color planes.
/// </summary>
public const int MaxPlanes = 3;
/// <summary>
/// Number of reference frame types (including intra type).
/// The number of reference-frame types, including the intra type.
/// </summary>
public const int TotalReferencesPerFrame = 8;
/// <summary>
/// Number of values for palette_size.
/// The maximum palette size.
/// </summary>
public const int PaletteMaxSize = 8;
/// <summary>
/// Maximum transform size categories.
/// The number of transform-size probability categories.
/// </summary>
public const int MaxTransformCategories = 4;
/// <summary>
/// The number of cumulative coefficient-level magnitude contexts.
/// </summary>
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;
/// <summary>
/// The maximum coefficient magnitude increment represented by base-range symbols.
/// </summary>
public const int CoefficientBaseRange = 12;
/// <summary>
/// The maximum transform dimension in samples.
/// </summary>
public const int MaxTransformSize = 1 << 6;
/// <summary>
/// The maximum transform dimension in units of four samples.
/// </summary>
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;
/// <summary>
/// The mask selecting the cumulative coefficient-level magnitude bits.
/// </summary>
public const int CoefficientContextMask = (1 << CoefficientContextBitCount) - 1;
/// <summary>
/// The base-two logarithm of the horizontal coefficient-context padding.
/// </summary>
public const int TransformPadHorizontalLog2 = 2;
/// <summary>
/// The horizontal coefficient-context padding in elements.
/// </summary>
public const int TransformPadHorizontal = 1 << TransformPadHorizontalLog2;
/// <summary>
/// The total vertical coefficient-context padding in rows.
/// </summary>
public const int TransformPadVertical = 6;
/// <summary>
/// The trailing coefficient-context padding in elements.
/// </summary>
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;
/// <summary>
/// The coefficient-context padding above a transform.
/// </summary>
public const int TransformPadTop = 2;
/// <summary>
/// The coefficient-context padding below a transform.
/// </summary>
public const int TransformPadBottom = 4;
/// <summary>
/// The largest symbol in a coefficient base-range distribution.
/// </summary>
public const int BaseRangeSizeMinus1 = 3;
/// <summary>
/// The largest coefficient magnitude represented before Golomb coding.
/// </summary>
public const int MaxBaseRange = 15;
/// <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>
public const int ChromaFromLumaAlphabetSizeLog2 = 4;
/// <summary>
/// Total number of Quantification Matrices sets stored.
/// The number of quantization-matrix levels.
/// </summary>
public const int QuantificationMatrixLevelCount = 1 << 4;
/// <summary>
/// The fixed-point precision of each quantization-matrix element.
/// </summary>
public const int QuantizationMatrixElementBitCount = 5;
/// <summary>
/// The directional intra-prediction angle increment in degrees.
/// </summary>
public const int AngleStep = 3;
/// <summary>
/// Maximum number of stages in a 1-dimensioanl transform function.
/// The maximum number of stages in a one-dimensional transform function.
/// </summary>
public const int MaxTransformStageNumber = 12;
/// <summary>
/// The number of partition contexts per block-size logarithm.
/// </summary>
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;
/// <summary>
/// The highest variable-transform depth index.
/// </summary>
public const int MaxVarTransform = 2;
/// <summary>
/// Maximum number of transform blocks per depth
/// The maximum number of transform blocks at one depth.
/// </summary>
public const int MaxTransformBlockCount = 16;
@ -203,5 +286,8 @@ internal static class Av1Constants
/// </summary>
public const int PlaneTypeCount = 2;
/// <summary>
/// The maximum number of transform units stored for one encoded block.
/// </summary>
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;
/// <summary>
/// Decodes one AV1 still-image elementary stream into an ImageSharp image.
/// </summary>
internal class Av1Decoder : IAv1TileReader
{
/// <summary>
/// The open-bitstream-unit parser for the current image item.
/// </summary>
private readonly ObuReader obuReader;
/// <summary>
/// The configuration used for decoded image and scratch-memory allocation.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// The tile parser shared by all tile groups in the current frame.
/// </summary>
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)
{
this.configuration = configuration;
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; }
/// <summary>
/// Gets the decoded sequence header, or <see langword="null"/> before the stream provides one.
/// </summary>
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; }
/// <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)
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)
{
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);
}

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

@ -9,19 +9,54 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Buffer for the pixels of a single frame.
/// Owns the padded luma and chroma sample planes for one decoded AV1 frame.
/// </summary>
/// <typeparam name="T">The unmanaged storage-element type used by the plane allocations.</typeparam>
internal class Av1FrameBuffer<T> : IDisposable
where T : unmanaged
{
/// <summary>
/// The number of border samples reserved for intra prediction and in-loop filtering.
/// </summary>
private const int DecoderPaddingValue = 72;
/// <summary>
/// The allocation-mask bit for the luma plane.
/// </summary>
private const int PictureBufferYFlag = 1 << 0;
/// <summary>
/// The allocation-mask bit for the first chroma plane.
/// </summary>
private const int PictureBufferCbFlag = 1 << 1;
/// <summary>
/// The allocation-mask bit for the second chroma plane.
/// </summary>
private const int PictureBufferCrFlag = 1 << 2;
/// <summary>
/// The allocation mask for a monochrome frame.
/// </summary>
private const int PictureBufferLumaMask = PictureBufferYFlag;
/// <summary>
/// The allocation mask for a frame containing all three planes.
/// </summary>
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;
/// <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)
{
Av1ColorFormat colorFormat = sequenceHeader.ColorConfig.IsMonochrome ? Av1ColorFormat.Yuv400 : maxColorFormat;
@ -36,7 +71,7 @@ internal class Av1FrameBuffer<T> : 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<T> : 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<T> : 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<T>(strideY * this.storageElementsPerSample, heightY);
}
if ((this.BufferEnableMask & PictureBufferCbFlag) != 0)
if ((bufferEnableMask & PictureBufferCbFlag) != 0)
{
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.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; }
/// <summary>
@ -115,12 +143,6 @@ internal class Av1FrameBuffer<T> : IDisposable
/// </summary>
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>
/// Gets or sets the horizontal padding distance.
/// </summary>
@ -132,22 +154,17 @@ internal class Av1FrameBuffer<T> : IDisposable
public int OriginY { get; set; }
/// <summary>
/// Gets or sets the vertical bottom padding distance
/// </summary>
public int OriginOriginY { get; set; }
/// <summary>
/// Gets or sets the Luma picture width, which excludes the padding.
/// Gets or sets the luma picture width, excluding padding.
/// </summary>
public int Width { get; set; }
/// <summary>
/// Gets or sets the Luma picture height, which excludes the padding.
/// Gets or sets the luma picture height, excluding padding.
/// </summary>
public int Height { get; set; }
/// <summary>
/// Gets or sets the Lume picture width.
/// Gets or sets the maximum luma picture width.
/// </summary>
public int MaxWidth { get; set; }
@ -167,33 +184,23 @@ internal class Av1FrameBuffer<T> : IDisposable
public ObuColorConfig ColorConfig { get; }
/// <summary>
/// Gets or sets the chroma subsampling.
/// Gets or sets the luma and chroma plane sampling layout.
/// </summary>
public Av1ColorFormat ColorFormat { get; set; }
/// <summary>
/// Gets or sets the Luma picture height.
/// Gets or sets the maximum luma picture height.
/// </summary>
public int MaxHeight { get; set; }
public int LumaSize { get; }
public int ChromaSize { get; }
/// <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>
public bool PackedFlag { get; set; }
public bool Is16BitPipeline { get; }
/// <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>
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<T> : 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;
}
/// <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>
/// <remarks>
/// SVT: svt_aom_derive_blk_pointers
/// </remarks>
/// <param name="plane">The luma or chroma plane.</param>
/// <param name="locationInPixels">The block origin in plane samples.</param>
/// <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)
{
this.GetPlaneLayout(
@ -233,7 +237,7 @@ internal class Av1FrameBuffer<T> : 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<T> : IDisposable
}
/// <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>
/// <remarks>
/// SVT: svt_aom_derive_blk_pointers
/// </remarks>
/// <param name="plane">The luma or chroma plane.</param>
/// <param name="locationInPixels">The block origin in plane samples.</param>
/// <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)
{
this.GetPlaneLayout(
@ -267,11 +274,12 @@ internal class Av1FrameBuffer<T> : IDisposable
}
/// <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>
/// <remarks>
/// SVT: svt_aom_derive_blk_pointers
/// </remarks>
/// <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>
/// <returns>The plane region excluding decoder padding.</returns>
public Buffer2DRegion<T> DeriveBlockPointer(Av1Plane plane, int subX, int subY)
{
this.GetPlaneLayout(
@ -294,8 +302,13 @@ internal class Av1FrameBuffer<T> : IDisposable
}
/// <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>
/// <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)
{
this.GetPlaneLayout(
@ -312,6 +325,17 @@ internal class Av1FrameBuffer<T> : IDisposable
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(
Av1Plane plane,
int subX,

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

@ -3,14 +3,22 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Provides the integer arithmetic primitives used by AV1 syntax and reconstruction.
/// </summary>
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)
{
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;
}
/// <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)
{
int result = 0;
@ -38,10 +51,10 @@ internal static class Av1Math
}
/// <summary>
/// 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.
/// </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)
{
uint log = 0;
@ -60,6 +73,11 @@ internal static class Av1Math
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)
{
uint s = 0;
@ -72,6 +90,11 @@ internal static class Av1Math
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)
{
if (value < 2)
@ -90,13 +113,39 @@ internal static class Av1Math
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) =>
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));
/// <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));
/// <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)
{
if (n == 0)
@ -107,6 +156,12 @@ internal static class Av1Math
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)
{
if (value < 0)
@ -117,37 +172,102 @@ internal static class Av1Math
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)
{
int mask = (1 << n) - 1;
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;
/// <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)
=> 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)
=> 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)
=> 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)
=> (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)
=> (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;
/// <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;
/// <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)
=> (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)
{
DebugGuard.MustBeGreaterThanOrEqualTo(bit, 1, nameof(bit));
@ -155,15 +275,35 @@ internal static class Av1Math
}
/// <summary>
/// <paramref name="a"/> implies <paramref name="b"/>.
/// Evaluates logical implication from one Boolean condition to another.
/// </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;
/// <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)
=> (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)
=> 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;
}

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

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

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

@ -3,8 +3,14 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Provides child-block geometry for AV1 partition types.
/// </summary>
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 = [
[
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)
=> PartitionSubSize[(int)partition][(int)blockSize];
}

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

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

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

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

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

@ -1792,7 +1792,7 @@ internal static class Av1DefaultDistributions
];
/// <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>
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];
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
/// </summary>
/// <param name="transformSizeContext">The square transform-size probability context.</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>
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;
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++)
{

2
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

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)
{
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);
stream.Write(lengthBytes, 0, lengthLength);
stream.Write(payload);

Loading…
Cancel
Save