diff --git a/.gitattributes b/.gitattributes
index b5f742ab47..1ab893e0a8 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -136,3 +136,6 @@
*.ico filter=lfs diff=lfs merge=lfs -text
*.cur filter=lfs diff=lfs merge=lfs -text
*.ani filter=lfs diff=lfs merge=lfs -text
+*.heic filter=lfs diff=lfs merge=lfs -text
+*.hif filter=lfs diff=lfs merge=lfs -text
+*.avif filter=lfs diff=lfs merge=lfs -text
diff --git a/ImageSharp.sln b/ImageSharp.sln
index 7ccd92c07d..1789a8d5d4 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -37,8 +37,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{815C0625-CD3
ProjectSection(SolutionItems) = preProject
src\Directory.Build.props = src\Directory.Build.props
src\Directory.Build.targets = src\Directory.Build.targets
- src\README.md = src\README.md
src\ImageSharp.ruleset = src\ImageSharp.ruleset
+ src\README.md = src\README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}"
@@ -215,6 +215,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B68
ProjectSection(SolutionItems) = preProject
tests\Images\Input\Jpg\issues\issue-1076-invalid-subsampling.jpg = tests\Images\Input\Jpg\issues\issue-1076-invalid-subsampling.jpg
tests\Images\Input\Jpg\issues\issue-1221-identify-multi-frame.jpg = tests\Images\Input\Jpg\issues\issue-1221-identify-multi-frame.jpg
+ tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg
tests\Images\Input\Jpg\issues\issue1006-incorrect-resize.jpg = tests\Images\Input\Jpg\issues\issue1006-incorrect-resize.jpg
tests\Images\Input\Jpg\issues\issue1049-exif-resize.jpg = tests\Images\Input\Jpg\issues\issue1049-exif-resize.jpg
tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Bedroom.jpg = tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Bedroom.jpg
@@ -238,7 +239,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B68
tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg = tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg
tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg = tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg
tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg = tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg
- tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fuzz", "fuzz", "{516A3532-6AC2-417B-AD79-9BD5D0D378A0}"
@@ -661,6 +661,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Qoi", "Qoi", "{E801B508-493
tests\Images\Input\Qoi\wikipedia_008.qoi = tests\Images\Input\Qoi\wikipedia_008.qoi
EndProjectSection
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Heif", "Heif", "{BA5D603A-C84C-43E5-B300-8BB886B02936}"
+ ProjectSection(SolutionItems) = preProject
+ tests\Images\Input\Heif\dwsample-heic-640.heic = tests\Images\Input\Heif\dwsample-heic-640.heic
+ tests\Images\Input\Heif\image1.heic = tests\Images\Input\Heif\image1.heic
+ tests\Images\Input\Heif\image2.heic = tests\Images\Input\Heif\image2.heic
+ tests\Images\Input\Heif\image3.heic = tests\Images\Input\Heif\image3.heic
+ tests\Images\Input\Heif\image4.heic = tests\Images\Input\Heif\image4.heic
+ tests\Images\Input\Heif\IMG-20230508-0053.hif = tests\Images\Input\Heif\IMG-20230508-0053.hif
+ tests\Images\Input\Heif\Irvine_CA.avif = tests\Images\Input\Heif\Irvine_CA.avif
+ tests\Images\Input\Heif\jpeg444_xnconvert.avif = tests\Images\Input\Heif\jpeg444_xnconvert.avif
+ tests\Images\Input\Heif\Orange4x4.avif = tests\Images\Input\Heif\Orange4x4.avif
+ EndProjectSection
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icon", "Icon", "{95E45DDE-A67D-48AD-BBA8-5FAA151B860D}"
ProjectSection(SolutionItems) = preProject
tests\Images\Input\Icon\aero_arrow.cur = tests\Images\Input\Icon\aero_arrow.cur
@@ -720,6 +733,7 @@ Global
{670DD46C-82E9-499A-B2D2-00A802ED0141} = {E1C42A6F-913B-4A7B-B1A8-2BB62843B254}
{5DFC394F-136F-4B76-9BCA-3BA786515EFC} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{E801B508-4935-41CD-BA85-CF11BFF55A45} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
+ {BA5D603A-C84C-43E5-B300-8BB886B02936} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{95E45DDE-A67D-48AD-BBA8-5FAA151B860D} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/shared-infrastructure b/shared-infrastructure
index 1dbfb576c8..922c5b21e5 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit 1dbfb576c83507645265c79e03369b66cdc0379f
+Subproject commit 922c5b21e5dfa02d4ef0d95334ab01c87a7a4309
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index 1d9f3bb85d..228e42bfe4 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -6,6 +6,7 @@
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.Formats.Gif;
+using SixLabors.ImageSharp.Formats.Heif;
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
@@ -213,6 +214,7 @@ public void Configure(IImageFormatConfigurationModule configuration)
/// .
/// .
/// .
+ /// .
///
/// The default configuration of .
internal static Configuration CreateDefaultInstance() => new(
@@ -225,6 +227,7 @@ public void Configure(IImageFormatConfigurationModule configuration)
new TiffConfigurationModule(),
new WebpConfigurationModule(),
new QoiConfigurationModule(),
+ new HeifConfigurationModule(),
new IcoConfigurationModule(),
new CurConfigurationModule());
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs
new file mode 100644
index 0000000000..28d48b13cf
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal enum Av1BitDepth : int
+{
+ EightBit = 0,
+ TenBit = 1,
+ TwelveBit = 2,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs
new file mode 100644
index 0000000000..615ed0b8d5
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal static class Av1BitDepthExtensions
+{
+ public static int GetBitCount(this Av1BitDepth bitDepth) => 8 + ((int)bitDepth << 1);
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs
new file mode 100644
index 0000000000..1b96c26d9e
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs
@@ -0,0 +1,157 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal ref struct Av1BitStreamReader
+{
+ private readonly Span data;
+
+ public Av1BitStreamReader(Span data) => this.data = data;
+
+ public int BitPosition { get; private set; } = 0;
+
+ ///
+ /// Gets the number of bytes in the readers buffer.
+ ///
+ public readonly int Length => this.data.Length;
+
+ public void Reset() => this.BitPosition = 0;
+
+ public void Skip(int bitCount) => this.BitPosition += bitCount;
+
+ public uint ReadLiteral(int bitCount)
+ {
+ DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount));
+
+ uint literal = 0;
+ for (int bit = bitCount - 1; bit >= 0; bit--)
+ {
+ literal |= this.ReadBit() << bit;
+ }
+
+ return literal;
+ }
+
+ internal uint ReadBit()
+ {
+ int byteOffset = Av1Math.DivideBy8Floor(this.BitPosition);
+ byte shift = (byte)(7 - Av1Math.Modulus8(this.BitPosition));
+ this.BitPosition++;
+ return (uint)((this.data[byteOffset] >> shift) & 0x01);
+ }
+
+ internal bool ReadBoolean() => this.ReadLiteral(1) > 0;
+
+ 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;
+ length = 0;
+ for (int i = 0; i < 56; i += 7)
+ {
+ uint leb128Byte = this.ReadLiteral(8);
+ value |= (leb128Byte & 0x7FUL) << i;
+ length++;
+ if ((leb128Byte & 0x80U) == 0)
+ {
+ break;
+ }
+ }
+
+ return value;
+ }
+
+ public uint ReadUnsignedVariableLength()
+ {
+ // See section 4.10.3 of the AV1-Specification
+ int leadingZerosCount = 0;
+ while (leadingZerosCount < 32)
+ {
+ uint bit = this.ReadLiteral(1);
+ if (bit == 1)
+ {
+ break;
+ }
+
+ leadingZerosCount++;
+ }
+
+ if (leadingZerosCount == 32)
+ {
+ return uint.MaxValue;
+ }
+
+ if (leadingZerosCount != 0)
+ {
+ uint basis = (1U << leadingZerosCount) - 1U;
+ uint value = this.ReadLiteral(leadingZerosCount);
+ return basis + value;
+ }
+
+ return 0;
+ }
+
+ public uint ReadNonSymmetric(uint n)
+ {
+ // See section 4.10.7 of the AV1-Specification
+ if (n <= 1)
+ {
+ return 0;
+ }
+
+ int w = (int)(Av1Math.FloorLog2(n) + 1);
+ uint m = (uint)((1 << w) - n);
+ uint v = this.ReadLiteral(w - 1);
+ if (v < m)
+ {
+ return v;
+ }
+
+ return (v << 1) - m + this.ReadLiteral(1);
+ }
+
+ 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;
+ signedValue = (int)((long)value - (signMask << 1));
+ }
+ else
+ {
+ signedValue = (int)value;
+ }
+
+ return signedValue;
+ }
+
+ 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;
+ for (int i = 0; i < 8 * n; i += 8)
+ {
+ t += this.ReadLiteral(8) << i;
+ }
+
+ return t;
+ }
+
+ public Span GetSymbolReader(int tileDataSize)
+ {
+ DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Symbol reading needs to start on byte boundary.");
+ int bytesRead = Av1Math.DivideBy8Floor(this.BitPosition);
+ Span span = this.data.Slice(bytesRead, tileDataSize);
+ this.Skip(tileDataSize << 3);
+ return span;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
new file mode 100644
index 0000000000..687b973331
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
@@ -0,0 +1,183 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal ref struct Av1BitStreamWriter
+{
+ private const int WordSize = 8;
+ private readonly AutoExpandingMemory memory;
+ private Span span;
+ private int capacityTrigger;
+ private byte buffer = 0;
+
+ public Av1BitStreamWriter(AutoExpandingMemory memory)
+ {
+ this.memory = memory;
+ this.span = memory.GetEntireSpan();
+ this.capacityTrigger = memory.Capacity - 1;
+ }
+
+ public int BitPosition { get; private set; } = 0;
+
+ public readonly int Capacity => this.memory.Capacity;
+
+ public static int GetLittleEndianBytes128(uint value, Span span)
+ {
+ if (value < 0x80U)
+ {
+ span[0] = (byte)value;
+ return 1;
+ }
+ else if (value < 0x8000U)
+ {
+ span[0] = (byte)((value & 0x7fU) | 0x80U);
+ span[1] = (byte)((value >> 7) & 0xff);
+ return 2;
+ }
+ else if (value < 0x800000U)
+ {
+ 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.");
+ }
+ }
+
+ public void Skip(int bitCount)
+ {
+ this.BitPosition += bitCount;
+ while (this.BitPosition >= WordSize)
+ {
+ this.BitPosition -= WordSize;
+ this.WriteBuffer();
+ }
+ }
+
+ public void Flush()
+ {
+ if (Av1Math.Modulus8(this.BitPosition) != 0)
+ {
+ // Flush a partial byte also.
+ this.WriteBuffer();
+ }
+
+ this.BitPosition = 0;
+ }
+
+ public void WriteLiteral(uint value, int bitCount)
+ {
+ for (int bit = bitCount - 1; bit >= 0; bit--)
+ {
+ this.WriteBit((byte)((value >> bit) & 0x1));
+ }
+ }
+
+ internal void WriteBoolean(bool value)
+ {
+ byte boolByte = value ? (byte)1 : (byte)0;
+ this.WriteBit(boolByte);
+ }
+
+ public void WriteSignedFromUnsigned(int signedValue, int n)
+ {
+ // See section 4.10.6 of the AV1-Specification
+ ulong value = (ulong)signedValue;
+ if (signedValue < 0)
+ {
+ value += 1UL << n;
+ }
+
+ this.WriteLiteral((uint)value, n);
+ }
+
+ public void WriteLittleEndianBytes128(uint value)
+ {
+ int bytesWritten = GetLittleEndianBytes128(value, this.span.Slice(this.BitPosition >> 3));
+ this.BitPosition += bytesWritten << 3;
+ }
+
+ internal void WriteNonSymmetric(uint value, uint numberOfSymbols)
+ {
+ // See section 4.10.7 of the AV1-Specification
+ if (numberOfSymbols <= 1)
+ {
+ return;
+ }
+
+ int w = (int)(Av1Math.FloorLog2(numberOfSymbols) + 1);
+ uint m = (uint)((1 << w) - numberOfSymbols);
+ if (value < m)
+ {
+ this.WriteLiteral(value, w - 1);
+ }
+ else
+ {
+ uint extraBit = ((value + m) >> 1) - value;
+ uint k = (value + m - extraBit) >> 1;
+ this.WriteLiteral(k, w - 1);
+ this.WriteLiteral(extraBit, 1);
+ }
+ }
+
+ private void WriteBit(byte value)
+ {
+ int bit = this.BitPosition & 0x07;
+ this.buffer = (byte)(((value << (7 - bit)) & 0xff) | this.buffer);
+ if (bit == 7)
+ {
+ this.WriteBuffer();
+ }
+
+ this.BitPosition++;
+ }
+
+ 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;
+ for (int i = 0; i < n; i++)
+ {
+ this.WriteLiteral(t & 0xff, 8);
+ t >>= 8;
+ }
+ }
+
+ internal void WriteBlob(Span tileData)
+ {
+ DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Tile Data only allowed on byte alignment");
+
+ int wordPosition = this.BitPosition >> 3;
+ if (this.span.Length <= wordPosition + tileData.Length)
+ {
+ this.memory.GetSpan(wordPosition + tileData.Length);
+ this.span = this.memory.GetEntireSpan();
+ }
+
+ tileData.CopyTo(this.span[wordPosition..]);
+ this.BitPosition += tileData.Length << 3;
+ }
+
+ private void WriteBuffer()
+ {
+ int wordPosition = Av1Math.DivideBy8Floor(this.BitPosition);
+ if (wordPosition > this.capacityTrigger)
+ {
+ // Expand the memory allocation.
+ this.memory.GetSpan(wordPosition + 1);
+ this.span = this.memory.GetEntireSpan();
+ this.capacityTrigger = this.span.Length - 1;
+ }
+
+ this.span[wordPosition] = this.buffer;
+ this.buffer = 0;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs
new file mode 100644
index 0000000000..62c4ff59e7
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs
@@ -0,0 +1,79 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal enum Av1BlockSize : byte
+{
+ // See sction 6.10.4 of the Av1 Specification.
+
+ /// A block of samples, 4 samples wide and 4 samples high.
+ Block4x4 = 0,
+
+ /// A block of samples, 4 samples wide and 8 samples high.
+ Block4x8 = 1,
+
+ /// A block of samples, 8 samples wide and 4 samples high.
+ Block8x4 = 2,
+
+ /// A block of samples, 8 samples wide and 8 samples high.
+ Block8x8 = 3,
+
+ /// A block of samples, 8 samples wide and 16 samples high.
+ Block8x16 = 4,
+
+ /// A block of samples, 16 samples wide and 8 samples high.
+ Block16x8 = 5,
+
+ /// A block of samples, 16 samples wide and 16 samples high.
+ Block16x16 = 6,
+
+ /// A block of samples, 16 samples wide and 32 samples high.
+ Block16x32 = 7,
+
+ /// A block of samples, 32 samples wide and 16 samples high.
+ Block32x16 = 8,
+
+ /// A block of samples, 32 samples wide and 32 samples high.
+ Block32x32 = 9,
+
+ /// A block of samples, 32 samples wide and 64 samples high.
+ Block32x64 = 10,
+
+ /// A block of samples, 64 samples wide and 32 samples high.
+ Block64x32 = 11,
+
+ /// A block of samples, 64 samples wide and 64 samples high.
+ Block64x64 = 12,
+
+ /// A block of samples, 64 samples wide and 128 samples high.
+ Block64x128 = 13,
+
+ /// A block of samples, 128 samples wide and 64 samples high.
+ Block128x64 = 14,
+
+ /// A block of samples, 128 samples wide and 128 samples high.
+ Block128x128 = 15,
+
+ /// A block of samples, 4 samples wide and 16 samples high.
+ Block4x16 = 16,
+
+ /// A block of samples, 16 samples wide and 4 samples high.
+ Block16x4 = 17,
+
+ /// A block of samples, 8 samples wide and 32 samples high.
+ Block8x32 = 18,
+
+ /// A block of samples, 32 samples wide and 8 samples high.
+ Block32x8 = 19,
+
+ /// A block of samples, 16 samples wide and 64 samples high.
+ Block16x64 = 20,
+
+ /// A block of samples, 64 samples wide and 16 samples high.
+ Block64x16 = 21,
+ AllSizes = 22,
+ SizeS = Block4x16,
+ Invalid = 255,
+ Largest = SizeS - 1,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
new file mode 100644
index 0000000000..6029d4fddc
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
@@ -0,0 +1,129 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal static class Av1BlockSizeExtensions
+{
+ 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[] 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).
+ private static readonly Av1BlockSize[][][] SubSampled =
+ [
+
+ // ss_x == 0 ss_x == 0 ss_x == 1 ss_x == 1
+ // ss_y == 0 ss_y == 1 ss_y == 0 ss_y == 1
+ [[Av1BlockSize.Block4x4, Av1BlockSize.Block4x4], [Av1BlockSize.Block4x4, Av1BlockSize.Block4x4]],
+ [[Av1BlockSize.Block4x8, Av1BlockSize.Block4x4], [Av1BlockSize.Invalid, Av1BlockSize.Block4x4]],
+ [[Av1BlockSize.Block8x4, Av1BlockSize.Invalid], [Av1BlockSize.Block4x4, Av1BlockSize.Block4x4]],
+ [[Av1BlockSize.Block8x8, Av1BlockSize.Block8x4], [Av1BlockSize.Block4x8, Av1BlockSize.Block4x4]],
+ [[Av1BlockSize.Block8x16, Av1BlockSize.Block8x8], [Av1BlockSize.Invalid, Av1BlockSize.Block4x8]],
+ [[Av1BlockSize.Block16x8, Av1BlockSize.Invalid], [Av1BlockSize.Block8x8, Av1BlockSize.Block8x4]],
+ [[Av1BlockSize.Block16x16, Av1BlockSize.Block16x8], [Av1BlockSize.Block8x16, Av1BlockSize.Block8x8]],
+ [[Av1BlockSize.Block16x32, Av1BlockSize.Block16x16], [Av1BlockSize.Invalid, Av1BlockSize.Block8x16]],
+ [[Av1BlockSize.Block32x16, Av1BlockSize.Invalid], [Av1BlockSize.Block16x16, Av1BlockSize.Block16x8]],
+ [[Av1BlockSize.Block32x32, Av1BlockSize.Block32x16], [Av1BlockSize.Block16x32, Av1BlockSize.Block16x16]],
+ [[Av1BlockSize.Block32x64, Av1BlockSize.Block32x32], [Av1BlockSize.Invalid, Av1BlockSize.Block16x32]],
+ [[Av1BlockSize.Block64x32, Av1BlockSize.Invalid], [Av1BlockSize.Block32x32, Av1BlockSize.Block32x16]],
+ [[Av1BlockSize.Block64x64, Av1BlockSize.Block64x32], [Av1BlockSize.Block32x64, Av1BlockSize.Block32x32]],
+ [[Av1BlockSize.Block64x128, Av1BlockSize.Block64x64], [Av1BlockSize.Invalid, Av1BlockSize.Block32x64]],
+ [[Av1BlockSize.Block128x64, Av1BlockSize.Invalid], [Av1BlockSize.Block64x64, Av1BlockSize.Block64x32]],
+ [[Av1BlockSize.Block128x128, Av1BlockSize.Block128x64], [Av1BlockSize.Block64x128, Av1BlockSize.Block64x64]],
+ [[Av1BlockSize.Block4x16, Av1BlockSize.Block4x8], [Av1BlockSize.Invalid, Av1BlockSize.Block4x8]],
+ [[Av1BlockSize.Block16x4, Av1BlockSize.Invalid], [Av1BlockSize.Block8x4, Av1BlockSize.Block8x4]],
+ [[Av1BlockSize.Block8x32, Av1BlockSize.Block8x16], [Av1BlockSize.Invalid, Av1BlockSize.Block4x16]],
+ [[Av1BlockSize.Block32x8, Av1BlockSize.Invalid], [Av1BlockSize.Block16x8, Av1BlockSize.Block16x4]],
+ [[Av1BlockSize.Block16x64, Av1BlockSize.Block16x32], [Av1BlockSize.Invalid, Av1BlockSize.Block8x32]],
+ [[Av1BlockSize.Block64x16, Av1BlockSize.Invalid], [Av1BlockSize.Block32x16, Av1BlockSize.Block32x8]]
+ ];
+
+ private static readonly Av1TransformSize[] MaxTransformSize = [
+ Av1TransformSize.Size4x4, Av1TransformSize.Size4x8, Av1TransformSize.Size8x4, Av1TransformSize.Size8x8,
+ Av1TransformSize.Size8x16, Av1TransformSize.Size16x8, Av1TransformSize.Size16x16, Av1TransformSize.Size16x32,
+ Av1TransformSize.Size32x16, Av1TransformSize.Size32x32, Av1TransformSize.Size32x64, Av1TransformSize.Size64x32,
+ Av1TransformSize.Size64x64, Av1TransformSize.Size64x64, Av1TransformSize.Size64x64, Av1TransformSize.Size64x64,
+ Av1TransformSize.Size4x16, Av1TransformSize.Size16x4, Av1TransformSize.Size8x32, Av1TransformSize.Size32x8,
+ Av1TransformSize.Size16x64, Av1TransformSize.Size64x16
+ ];
+
+ 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];
+
+ public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize];
+
+ public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize];
+
+ ///
+ /// Returns the width of the block in samples.
+ ///
+ public static int GetWidth(this Av1BlockSize blockSize)
+ => Get4x4WideCount(blockSize) << 2;
+
+ ///
+ /// Returns of the height of the block in 4 samples.
+ ///
+ public static int GetHeight(this Av1BlockSize blockSize)
+ => Get4x4HighCount(blockSize) << 2;
+
+ ///
+ /// Returns base 2 logarithm of the width of the block in units of 4 samples.
+ ///
+ public static int Get4x4WidthLog2(this Av1BlockSize blockSize)
+ => Av1Math.Log2(Get4x4WideCount(blockSize));
+
+ ///
+ /// Returns base 2 logarithm of the height of the block in units of 4 samples.
+ ///
+ public static int Get4x4HeightLog2(this Av1BlockSize blockSize)
+ => Av1Math.Log2(Get4x4HighCount(blockSize));
+
+ ///
+ /// Returns the block size of a sub sampled block.
+ ///
+ public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, bool subX, bool subY)
+ => GetSubsampled(blockSize, subX ? 1 : 0, subY ? 1 : 0);
+
+ ///
+ /// Returns the block size of a sub sampled block.
+ ///
+ public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, int subX, int subY)
+ {
+ if (blockSize == Av1BlockSize.Invalid)
+ {
+ return Av1BlockSize.Invalid;
+ }
+
+ return SubSampled[(int)blockSize][subX][subY];
+ }
+
+ public static Av1TransformSize GetMaxUvTransformSize(this Av1BlockSize blockSize, bool subX, bool subY)
+ {
+ Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY);
+ Av1TransformSize uvTransformSize = Av1TransformSize.Invalid;
+ if (planeBlockSize < Av1BlockSize.AllSizes)
+ {
+ uvTransformSize = planeBlockSize.GetMaximumTransformSize();
+ }
+
+ return uvTransformSize switch
+ {
+ Av1TransformSize.Size64x64 or Av1TransformSize.Size64x32 or Av1TransformSize.Size32x64 => Av1TransformSize.Size32x32,
+ Av1TransformSize.Size64x16 => Av1TransformSize.Size32x16,
+ Av1TransformSize.Size16x64 => Av1TransformSize.Size16x32,
+ _ => uvTransformSize,
+ };
+ }
+
+ ///
+ /// Returns the largest transform size that can be used for blocks of given size.
+ /// The can be either a square or rectangular block.
+ ///
+ public static Av1TransformSize GetMaximumTransformSize(this Av1BlockSize blockSize)
+ => MaxTransformSize[(int)blockSize];
+
+ public static int GetPelsLog2Count(this Av1BlockSize blockSize)
+ => PelsLog2Count[(int)blockSize];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs
new file mode 100644
index 0000000000..e0cef47753
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+///
+/// Implementation of section 2.3.3 of AV1 Codec ISO Media File Format Binding specification v1.2.0.
+/// See https://aomediacodec.github.io/av1-isobmff/v1.2.0.html#av1codecconfigurationbox-syntax.
+///
+internal struct Av1CodecConfiguration
+{
+ public Av1CodecConfiguration(Span boxBuffer)
+ {
+ Av1BitStreamReader reader = new(boxBuffer);
+
+ this.Marker = (byte)reader.ReadLiteral(1);
+ this.Version = (byte)reader.ReadLiteral(7);
+ this.SeqProfile = (byte)reader.ReadLiteral(3);
+ this.SeqLevelIdx0 = (byte)reader.ReadLiteral(5);
+ this.SeqTier0 = (byte)reader.ReadLiteral(1);
+ this.HighBitdepth = (byte)reader.ReadLiteral(1);
+ this.TwelveBit = reader.ReadLiteral(1) == 1;
+ this.MonoChrome = reader.ReadLiteral(1) == 1;
+ this.ChromaSubsamplingX = reader.ReadLiteral(1) == 1;
+ this.ChromaSubsamplingY = reader.ReadLiteral(1) == 1;
+ this.ChromaSamplePosition = (byte)reader.ReadLiteral(2);
+
+ // 3 bits are reserved.
+ reader.ReadLiteral(3);
+
+ this.InitialPresentationDelayPresent = reader.ReadLiteral(1) == 1;
+ if (this.InitialPresentationDelayPresent)
+ {
+ byte initialPresentationDelayMinusOne = (byte)reader.ReadLiteral(4);
+ this.InitialPresentationDelay = (byte)(initialPresentationDelayMinusOne + 1);
+ }
+ }
+
+ public byte Marker { get; }
+
+ public byte Version { get; }
+
+ public byte SeqProfile { get; }
+
+ public byte SeqLevelIdx0 { get; }
+
+ public byte SeqTier0 { get; }
+
+ public byte HighBitdepth { get; }
+
+ public bool TwelveBit { get; }
+
+ public bool MonoChrome { get; }
+
+ public bool ChromaSubsamplingX { get; }
+
+ public bool ChromaSubsamplingY { get; }
+
+ public byte ChromaSamplePosition { get; }
+
+ public bool InitialPresentationDelayPresent { get; }
+
+ public byte InitialPresentationDelay { get; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs b/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs
new file mode 100644
index 0000000000..07be6a0442
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal enum Av1ColorFormat
+{
+ Yuv400,
+ Yuv420,
+ Yuv422,
+ Yuv444,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
new file mode 100644
index 0000000000..fc4915e537
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
@@ -0,0 +1,183 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal static class Av1Constants
+{
+ public const ObuSequenceProfile MaxSequenceProfile = ObuSequenceProfile.Professional;
+
+ public const int LevelBits = 5;
+
+ ///
+ /// Number of fractional bits for computing position in upscaling.
+ ///
+ public const int SuperResolutionScaleBits = 14;
+
+ public const int ScaleNumerator = -1;
+
+ ///
+ /// Number of reference frames that can be used for inter prediction.
+ ///
+ public const int ReferencesPerFrame = 7;
+
+ ///
+ /// Maximum area of a tile in units of luma samples.
+ ///
+ public const int MaxTileArea = 4096 * 2304;
+
+ ///
+ /// Maximum width of a tile in units of luma samples.
+ ///
+ public const int MaxTileWidth = 4096;
+
+ ///
+ /// Maximum number of tile columns.
+ ///
+ public const int MaxTileColumnCount = 64;
+
+ ///
+ /// Maximum number of tile rows.
+ ///
+ public const int MaxTileRowCount = 64;
+
+ ///
+ /// Number of frames that can be stored for future reference.
+ ///
+ public const int ReferenceFrameCount = 8;
+
+ ///
+ /// Value of 'PrimaryReferenceFrame' indicating that there is no primary reference frame.
+ ///
+ public const uint PrimaryReferenceFrameNone = 7;
+
+ public const int PimaryReferenceBits = 3;
+
+ ///
+ /// Number of segments allowed in segmentation map.
+ ///
+ public const int MaxSegmentCount = 8;
+
+ ///
+ /// Smallest denominator for upscaling ratio.
+ ///
+ public const int SuperResolutionScaleDenominatorMinimum = 9;
+
+ ///
+ /// Base 2 logarithm of maximum size of a superblock in luma samples.
+ ///
+ public const int MaxSuperBlockSizeLog2 = 7;
+
+ ///
+ /// Base 2 logarithm of smallest size of a mode info block.
+ ///
+ public const int ModeInfoSizeLog2 = 2;
+
+ public const int MaxQ = 255;
+
+ ///
+ /// Number of segmentation features.
+ ///
+ public const int SegmentationLevelMax = 8;
+
+ ///
+ /// Maximum size of a loop restoration tile.
+ ///
+ public const int RestorationMaxTileSize = 256;
+
+ ///
+ /// Number of Wiener coefficients to read.
+ ///
+ public const int WienerCoefficientCount = 3;
+
+ public const int FrameLoopFilterCount = 4;
+
+ ///
+ /// Value indicating alternative encoding of quantizer index delta values.
+ ///
+ public const int DeltaQuantizerSmall = 3;
+
+ ///
+ /// Value indicating alternative encoding of loop filter delta values.
+ ///
+ public const int DeltaLoopFilterSmall = 3;
+
+ ///
+ /// Maximum value used for loop filtering.
+ ///
+ public const int MaxLoopFilter = 63;
+
+ ///
+ /// Maximum magnitude of AngleDeltaY and AngleDeltaUV.
+ ///
+ public const int MaxAngleDelta = 3;
+
+ ///
+ /// Maximum number of color planes.
+ ///
+ public const int MaxPlanes = 3;
+
+ ///
+ /// Number of reference frame types (including intra type).
+ ///
+ public const int TotalReferencesPerFrame = 8;
+
+ ///
+ /// Number of values for palette_size.
+ ///
+ public const int PaletteMaxSize = 8;
+
+ ///
+ /// Maximum transform size categories.
+ ///
+ public const int MaxTransformCategories = 4;
+
+ public const int CoefficientContextCount = 6;
+
+ public const int BaseLevelsCount = 2;
+
+ public const int CoefficientBaseRange = 12;
+
+ public const int MaxTransformSize = 1 << 6;
+
+ public const int MaxTransformSizeUnit = MaxTransformSize >> 2;
+
+ public const int CoefficientContextBitCount = 6;
+
+ public const int CoefficientContextMask = (1 << CoefficientContextBitCount) - 1;
+
+ public const int TransformPadHorizontalLog2 = 2;
+
+ public const int TransformPadHorizontal = 1 << TransformPadHorizontalLog2;
+
+ public const int TransformPadVertical = 6;
+
+ public const int TransformPadEnd = 16;
+
+ public const int TransformPad2d = ((MaxTransformSize + TransformPadHorizontal) * (MaxTransformSize + TransformPadVertical)) + TransformPadEnd;
+
+ public const int TransformPadTop = 2;
+
+ public const int BaseRangeSizeMinus1 = 3;
+
+ public const int MaxBaseRange = 15;
+
+ ///
+ /// Log2 of number of values for ChromaFromLuma Alpha U and ChromaFromLuma Alpha V.
+ ///
+ public const int ChromaFromLumaAlphabetSizeLog2 = 4;
+
+ ///
+ /// Total number of Quantification Matrices sets stored.
+ ///
+ public const int QuantificationMatrixLevelCount = 4;
+
+ public const int AngleStep = 3;
+
+ ///
+ /// Maximum number of stages in a 1-dimensioanl transform function.
+ ///
+ public const int MaxTransformStageNumber = 12;
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
new file mode 100644
index 0000000000..e6ed9ab0fd
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal class Av1Decoder : IAv1TileReader
+{
+ private readonly ObuReader obuReader;
+ private readonly Configuration configuration;
+ private Av1TileReader? tileReader;
+ private Av1FrameDecoder? frameDecoder;
+
+ public Av1Decoder(Configuration configuration)
+ {
+ this.configuration = configuration;
+ this.obuReader = new();
+ }
+
+ public ObuFrameHeader? FrameHeader { get; private set; }
+
+ public ObuSequenceHeader? SequenceHeader { get; private set; }
+
+ public Av1FrameInfo? FrameInfo { get; private set; }
+
+ public Av1FrameBuffer? FrameBuffer { get; private set; }
+
+ public void Decode(Span buffer)
+ {
+ Av1BitStreamReader reader = new(buffer);
+ this.obuReader.ReadAll(ref reader, buffer.Length, this, false);
+ Guard.NotNull(this.tileReader, nameof(this.tileReader));
+ Guard.NotNull(this.SequenceHeader, nameof(this.SequenceHeader));
+ Guard.NotNull(this.FrameHeader, nameof(this.FrameHeader));
+
+ this.FrameInfo = this.tileReader.FrameInfo;
+ this.FrameBuffer = new(this.configuration, this.SequenceHeader, this.SequenceHeader.ColorConfig.GetColorFormat(), false);
+ this.frameDecoder = new(this.SequenceHeader, this.FrameHeader, this.FrameInfo, this.FrameBuffer);
+ this.frameDecoder.DecodeFrame();
+ }
+
+ public void ReadTile(Span tileData, int tileNum)
+ {
+ if (this.tileReader == null)
+ {
+ this.SequenceHeader = this.obuReader.SequenceHeader;
+ this.FrameHeader = this.obuReader.FrameHeader;
+ this.tileReader = new Av1TileReader(this.configuration, this.SequenceHeader!, this.FrameHeader!);
+ }
+
+ this.tileReader.ReadTile(tileData, tileNum);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs
new file mode 100644
index 0000000000..301804bf65
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs
@@ -0,0 +1,192 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+///
+/// Buffer for the pixels of a single frame.
+///
+internal class Av1FrameBuffer : IDisposable
+{
+ private const int DecoderPaddingValue = 72;
+ private const int PictureBufferYFlag = 1 << 0;
+ private const int PictureBufferCbFlag = 1 << 1;
+ private const int PictureBufferCrFlag = 1 << 2;
+ private const int PictureBufferLumaMask = PictureBufferYFlag;
+ private const int PictureBufferFullMask = PictureBufferYFlag | PictureBufferCbFlag | PictureBufferCrFlag;
+
+ public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHeader, Av1ColorFormat maxColorFormat, bool is16BitPipeline)
+ {
+ Av1ColorFormat colorFormat = sequenceHeader.ColorConfig.IsMonochrome ? Av1ColorFormat.Yuv400 : maxColorFormat;
+ this.MaxWidth = sequenceHeader.MaxFrameWidth;
+ this.MaxHeight = sequenceHeader.MaxFrameHeight;
+ this.BitDepth = sequenceHeader.ColorConfig.BitDepth;
+ int bitsPerPixel = this.BitDepth > Av1BitDepth.EightBit || is16BitPipeline ? 2 : 1;
+ this.ColorFormat = colorFormat;
+ this.BufferEnableMask = sequenceHeader.ColorConfig.IsMonochrome ? PictureBufferLumaMask : PictureBufferFullMask;
+
+ int leftPadding = DecoderPaddingValue;
+ int rightPadding = DecoderPaddingValue;
+ int topPadding = DecoderPaddingValue;
+ int bottomPadding = DecoderPaddingValue;
+
+ this.StartPosition = new Point(leftPadding, topPadding);
+
+ this.Width = this.MaxWidth;
+ this.Height = this.MaxHeight;
+ int strideY = this.MaxWidth + leftPadding + rightPadding;
+ int heightY = this.MaxHeight + topPadding + bottomPadding;
+ this.OriginX = leftPadding;
+ this.OriginY = topPadding;
+ this.OriginOriginY = bottomPadding;
+ int strideChroma = 0;
+ int heightChroma = 0;
+ switch (this.ColorFormat)
+ {
+ case Av1ColorFormat.Yuv420:
+ strideChroma = (strideY + 1) >> 1;
+ heightChroma = (this.Height + 1) >> 1;
+ break;
+ case Av1ColorFormat.Yuv422:
+ strideChroma = (strideY + 1) >> 1;
+ heightChroma = this.Height;
+ break;
+ case Av1ColorFormat.Yuv444:
+ strideChroma = strideY;
+ heightChroma = this.Height;
+ break;
+ }
+
+ this.PackedFlag = false;
+
+ this.BufferY = null;
+ this.BufferCb = null;
+ this.BufferCr = null;
+ if ((this.BufferEnableMask & PictureBufferYFlag) != 0)
+ {
+ this.BufferY = configuration.MemoryAllocator.Allocate2D(strideY * bitsPerPixel, heightY);
+ }
+
+ if ((this.BufferEnableMask & PictureBufferCbFlag) != 0)
+ {
+ this.BufferCb = configuration.MemoryAllocator.Allocate2D(strideChroma * bitsPerPixel, heightChroma);
+ }
+
+ if ((this.BufferEnableMask & PictureBufferCrFlag) != 0)
+ {
+ this.BufferCr = configuration.MemoryAllocator.Allocate2D(strideChroma * bitsPerPixel, heightChroma);
+ }
+
+ this.BitIncrementY = null;
+ this.BitIncrementCb = null;
+ this.BitIncrementCr = null;
+ this.BitIncrementY = null;
+ this.BitIncrementCb = null;
+ this.BitIncrementCr = null;
+ }
+
+ public Point StartPosition { get; private set; }
+
+ ///
+ /// Gets the Y luma buffer.
+ ///
+ public Buffer2D? BufferY { get; private set; }
+
+ ///
+ /// Gets the U chroma buffer.
+ ///
+ public Buffer2D? BufferCb { get; private set; }
+
+ ///
+ /// Gets the V chroma buffer.
+ ///
+ public Buffer2D? BufferCr { get; private set; }
+
+ public Buffer2D? BitIncrementY { get; private set; }
+
+ public Buffer2D? BitIncrementCb { get; private set; }
+
+ public Buffer2D? BitIncrementCr { get; private set; }
+
+ ///
+ /// Gets or sets the horizontal padding distance.
+ ///
+ public int OriginX { get; set; }
+
+ ///
+ /// Gets or sets the vertical padding distance.
+ ///
+ public int OriginY { get; set; }
+
+ ///
+ /// Gets or sets the vertical bottom padding distance
+ ///
+ public int OriginOriginY { get; set; }
+
+ ///
+ /// Gets or sets the Luma picture width, which excludes the padding.
+ ///
+ public int Width { get; set; }
+
+ ///
+ /// Gets or sets the Luma picture height, which excludes the padding.
+ ///
+ public int Height { get; set; }
+
+ ///
+ /// Gets or sets the Lume picture width.
+ ///
+ public int MaxWidth { get; set; }
+
+ ///
+ /// Gets or sets the pixel bit depth.
+ ///
+ public Av1BitDepth BitDepth { get; set; }
+
+ ///
+ /// Gets or sets the chroma subsampling.
+ ///
+ public Av1ColorFormat ColorFormat { get; set; }
+
+ ///
+ /// Gets or sets the Luma picture height.
+ ///
+ public int MaxHeight { get; set; }
+
+ public int LumaSize { get; }
+
+ public int ChromaSize { get; }
+
+ ///
+ /// Gets or sets a value indicating whether the bytes of the buffers are packed.
+ ///
+ public bool PackedFlag { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether film grain parameters are present for this frame.
+ ///
+ public bool FilmGrainFlag { get; set; }
+
+ public int BufferEnableMask { get; set; }
+
+ public bool Is16BitPipeline { get; set; }
+
+ public void Dispose()
+ {
+ this.BufferY?.Dispose();
+ this.BufferY = null;
+ this.BufferCb?.Dispose();
+ 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;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
new file mode 100644
index 0000000000..59da84eeea
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
@@ -0,0 +1,169 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal static class Av1Math
+{
+ public static int MostSignificantBit(uint value)
+ {
+ int log = 0;
+ int i;
+
+ Guard.IsTrue(value != 0, nameof(value), "Must have al least 1 bit set");
+
+ for (i = 4; i >= 0; --i)
+ {
+ int shift = 1 << i;
+ uint x = value >> shift;
+ if (x != 0)
+ {
+ value = x;
+ log += shift;
+ }
+ }
+
+ return log;
+ }
+
+ public static uint Log2(uint n)
+ {
+ uint result = 0U;
+ while ((n >>= 1) > 0)
+ {
+ result++;
+ }
+
+ return result;
+ }
+
+ public static int Log2(int n)
+ {
+ int result = 0;
+ while ((n >>= 1) > 0)
+ {
+ result++;
+ }
+
+ return result;
+ }
+
+ public static uint FloorLog2(uint value)
+ {
+ uint s = 0;
+ while (value != 0U)
+ {
+ value >>= 1;
+ s++;
+ }
+
+ return s - 1;
+ }
+
+ public static uint CeilLog2(uint value)
+ {
+ if (value < 2)
+ {
+ return 0;
+ }
+
+ uint i = 1;
+ uint p = 2;
+ while (p < value)
+ {
+ i++;
+ p <<= 1;
+ }
+
+ return i;
+ }
+
+ public static uint Clip1(uint value, int bitDepth) =>
+ Clip3(0, (1U << bitDepth) - 1, value);
+
+ public static uint Clip3(uint x, uint y, uint z)
+ {
+ if (z < x)
+ {
+ return x;
+ }
+
+ if (z > y)
+ {
+ return y;
+ }
+
+ return z;
+ }
+
+ public static int Clip3(int x, int y, int z)
+ {
+ if (z < x)
+ {
+ return x;
+ }
+
+ if (z > y)
+ {
+ return y;
+ }
+
+ return z;
+ }
+
+ public static uint Round2(uint value, int n)
+ {
+ if (n == 0)
+ {
+ return value;
+ }
+
+ return (uint)((value + (1 << (n - 1))) >> n);
+ }
+
+ public static int Round2(int value, int n)
+ {
+ if (value < 0)
+ {
+ value = -value;
+ }
+
+ return (int)Round2((uint)value, n);
+ }
+
+ internal static int AlignPowerOf2(int value, int n)
+ {
+ int mask = (1 << n) - 1;
+ return (value + mask) & ~mask;
+ }
+
+ internal static int RoundPowerOf2(int value, int n) => (value + ((1 << n) >> 1)) >> n;
+
+ internal static int Clamp(int value, int low, int high)
+ => value < low ? low : (value > high ? high : value);
+
+ internal static long Clamp(long value, long low, long high)
+ => value < low ? low : (value > high ? high : value);
+
+ internal static int DivideLog2Floor(int value, int n)
+ => value >> n;
+
+ internal static int DivideLog2Ceiling(int value, int n)
+ => (value + (1 << n) - 1) >> n;
+
+ // Last 3 bits are the value of mod 8.
+ internal static int Modulus8(int value) => value & 0x07;
+
+ internal static int DivideBy8Floor(int value) => value >> 3;
+
+ internal static int RoundPowerOf2Signed(int value, int n)
+ => (value < 0) ? -RoundPowerOf2(-value, n) : RoundPowerOf2(value, n);
+
+ internal static int RoundShift(long value, int bit)
+ {
+ DebugGuard.MustBeGreaterThanOrEqualTo(bit, 1, nameof(bit));
+ return (int)((value + (1L << (bit - 1))) >> bit);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs
new file mode 100644
index 0000000000..11f973a064
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs
@@ -0,0 +1,152 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal enum Av1PartitionType
+{
+ // See section 6.10.4 of Avi Spcification
+
+ ///
+ /// Not partitioned any further.
+ ///
+ ///
+ ///
+ /// ***
+ /// * *
+ /// ***
+ ///
+ ///
+ None = 0,
+
+ ///
+ /// Horizontally split in 2 partitions.
+ ///
+ ///
+ ///
+ /// ***
+ /// * *
+ /// ***
+ /// * *
+ /// ***
+ ///
+ ///
+ Horizontal = 1,
+
+ ///
+ /// Vertically split in 2 partitions.
+ ///
+ ///
+ ///
+ /// *****
+ /// * * *
+ /// *****
+ ///
+ ///
+ Vertical = 2,
+
+ ///
+ /// 4 equally sized partitions.
+ ///
+ ///
+ ///
+ /// *****
+ /// * * *
+ /// *****
+ /// * * *
+ /// *****
+ ///
+ ///
+ Split = 3,
+
+ ///
+ /// Horizontal split and the top partition is split again.
+ ///
+ ///
+ ///
+ /// *****
+ /// * * *
+ /// *****
+ /// * *
+ /// *****
+ ///
+ ///
+ HorizontalA = 4,
+
+ ///
+ /// Horizontal split and the bottom partition is split again.
+ ///
+ ///
+ ///
+ /// *****
+ /// * *
+ /// *****
+ /// * * *
+ /// *****
+ ///
+ ///
+ HorizontalB = 5,
+
+ ///
+ /// Vertical split and the left partition is split again.
+ ///
+ ///
+ ///
+ /// *****
+ /// * * *
+ /// *** *
+ /// * * *
+ /// *****
+ ///
+ ///
+ VerticalA = 6,
+
+ ///
+ /// Vertical split and the right partition is split again.
+ ///
+ ///
+ ///
+ /// *****
+ /// * * *
+ /// * ***
+ /// * * *
+ /// *****
+ ///
+ ///
+ VerticalB = 7,
+
+ ///
+ /// 4:1 horizontal partition.
+ ///
+ ///
+ ///
+ /// ***
+ /// * *
+ /// ***
+ /// * *
+ /// ***
+ /// * *
+ /// ***
+ /// * *
+ /// ***
+ ///
+ ///
+ Horizontal4 = 8,
+
+ ///
+ /// 4:1 vertical partition.
+ ///
+ ///
+ ///
+ /// *********
+ /// * * * * *
+ /// *********
+ ///
+ ///
+ Vertical4 = 9,
+
+ ///
+ /// Invalid value.
+ ///
+ Invalid = 255
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs
new file mode 100644
index 0000000000..99ba78c3ba
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs
@@ -0,0 +1,104 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal static class Av1PartitionTypeExtensions
+{
+ private static readonly Av1BlockSize[][] PartitionSubSize = [
+ [
+ Av1BlockSize.Block4x4,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x8,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x16,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x32,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x64,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x128,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
+ ], [
+ Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
+ ], [
+ Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
+ ], [
+ Av1BlockSize.Block4x4,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x4,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x8,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x16,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x32,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x64,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
+ ], [
+ Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
+ ], [
+ Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
+ ], [
+ Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
+ ], [
+ Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
+ ], [
+ Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x4,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x8,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x16,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ ], [
+ Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x16,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x32,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x64,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
+ ]
+ ];
+
+ public static Av1BlockSize GetBlockSubSize(this Av1PartitionType partition, Av1BlockSize blockSize)
+ => PartitionSubSize[(int)partition][(int)blockSize];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs
new file mode 100644
index 0000000000..9d73cda42a
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal enum Av1Plane : int
+{
+ Y = 0,
+ U = 1,
+ V = 2,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/IAv1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/IAv1TileReader.cs
new file mode 100644
index 0000000000..3bbef50bc7
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/IAv1TileReader.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+///
+/// Interface for reading of image tiles.
+///
+internal interface IAv1TileReader
+{
+ ///
+ /// Read the information for a single tile.
+ ///
+ ///
+ /// The bytes of encoded data in the bitstream dedicated to this tile.
+ ///
+ /// The index of the tile that is to be read.
+ void ReadTile(Span tileData, int tileNum);
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/IAv1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/IAv1TileWriter.cs
new file mode 100644
index 0000000000..1e2552b8b3
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/IAv1TileWriter.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+///
+/// Interface for writing of image tiles.
+///
+internal interface IAv1TileWriter
+{
+ ///
+ /// Write the information for a single tile.
+ ///
+ /// The index of the tile that is to be read.
+ ///
+ /// The bytes of encoded data in the bitstream dedicated to this tile.
+ ///
+ Span WriteTile(int tileNum);
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs
new file mode 100644
index 0000000000..bd71ea4334
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal enum ObuChromoSamplePosition : byte
+{
+ ///
+ /// Unknown.
+ ///
+ Unknown = 0,
+
+ ///
+ /// Horizontally co-located with luma(0, 0) sample, between two vertical samples.
+ ///
+ Vertical = 1,
+
+ ///
+ /// Co-located with luma(0, 0) sample
+ ///
+ Colocated = 2,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs
new file mode 100644
index 0000000000..24e7237a29
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs
@@ -0,0 +1,67 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuColorConfig
+{
+ private bool isMonochrome;
+
+ public bool IsColorDescriptionPresent { get; set; }
+
+ ///
+ /// Gets the number of color channels in this image. Can have the value 1 or 3.
+ ///
+ public int PlaneCount { get; private set; }
+
+ ///
+ /// Gets or sets a value indicating whether the image has a single greyscale plane, will have
+ /// color planes otherwise.
+ ///
+ public bool IsMonochrome
+ {
+ get => this.isMonochrome;
+ set
+ {
+ this.PlaneCount = value ? 1 : Av1Constants.MaxPlanes;
+ this.isMonochrome = value;
+ }
+ }
+
+ public ObuColorPrimaries ColorPrimaries { get; set; }
+
+ public ObuTransferCharacteristics TransferCharacteristics { get; set; }
+
+ public ObuMatrixCoefficients MatrixCoefficients { get; set; }
+
+ public bool ColorRange { get; set; }
+
+ public bool SubSamplingX { get; set; }
+
+ public bool SubSamplingY { get; set; }
+
+ public bool HasSeparateUvDelta { get; set; }
+
+ public ObuChromoSamplePosition ChromaSamplePosition { get; set; }
+
+ public Av1BitDepth BitDepth { get; set; }
+
+ public Av1ColorFormat GetColorFormat()
+ {
+ Av1ColorFormat format = Av1ColorFormat.Yuv400;
+ if (this.SubSamplingX && this.SubSamplingY)
+ {
+ format = Av1ColorFormat.Yuv420;
+ }
+ else if (this.SubSamplingX & !this.SubSamplingY)
+ {
+ format = Av1ColorFormat.Yuv422;
+ }
+ else if (!this.SubSamplingX && !this.SubSamplingY)
+ {
+ format = Av1ColorFormat.Yuv444;
+ }
+
+ return format;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs
new file mode 100644
index 0000000000..3136bba383
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal enum ObuColorPrimaries
+{
+ None = 0,
+ Bt709 = 1,
+ Unspecified = 2,
+ Bt470M = 4,
+ Bt470BG = 5,
+ Bt601 = 6,
+ Smpte240 = 7,
+ GenericFilm = 8,
+ Bt2020 = 9,
+ Xyz = 10,
+ Smpte431 = 11,
+ Smpte432 = 12,
+ Ebu3213 = 22,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs
new file mode 100644
index 0000000000..f952fae216
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuConstraintDirectionalEnhancementFilterParameters
+{
+ public int BitCount { get; internal set; }
+
+ public int Damping { get; internal set; }
+
+ public int[] YStrength { get; set; } = new int[16];
+
+ public int[] UvStrength { get; set; } = new int[16];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDecoderModelInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDecoderModelInfo.cs
new file mode 100644
index 0000000000..bc7f96d9ef
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDecoderModelInfo.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1;
+
+internal class ObuDecoderModelInfo
+{
+ ///
+ /// Gets or sets BufferDelayLength. Specifies the length of the decoder_buffer_delay and the encoder_buffer_delay
+ /// syntax elements, in bits.
+ ///
+ internal uint BufferDelayLength { get; set; }
+
+ ///
+ /// Gets or sets NumUnitsInDecodingTick. This is the number of time units of a decoding clock operating at the frequency time_scale Hz
+ /// that corresponds to one increment of a clock tick counter.
+ ///
+ internal uint NumUnitsInDecodingTick { get; set; }
+
+ ///
+ /// Gets or sets BufferRemovalTimeLength. Specifies the length of the buffer_removal_time syntax element, in bits.
+ ///
+ internal uint BufferRemovalTimeLength { get; set; }
+
+ ///
+ /// Gets or sets the FramePresentationTimeLength. Specifies the length of the frame_presentation_time syntax element, in bits.
+ ///
+ internal uint FramePresentationTimeLength { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs
new file mode 100644
index 0000000000..c92485eaf1
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuDeltaParameters
+{
+ public bool IsPresent { get; internal set; }
+
+ public int Resolution { get; internal set; }
+
+ public bool IsMulti { get; internal set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs
new file mode 100644
index 0000000000..42a3b6578d
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs
@@ -0,0 +1,172 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuFilmGrainParameters
+{
+ ///
+ /// Gets or sets a value indicating whether film grain should be added to this frame. A value equal to false specifies that film
+ /// grain should not be added.
+ ///
+ public bool ApplyGrain { get; set; }
+
+ ///
+ /// Gets or sets GrainSeed. This value specifies the starting value for the pseudo-random numbers used during film grain synthesis.
+ ///
+ public uint GrainSeed { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether a new set of parameters should be sent. A value equal to false means that the
+ /// previous set of parameters should be used.
+ ///
+ public bool UpdateGrain { get; set; }
+
+ ///
+ /// Gets or sets FilmGrainParamsRefIdx. Indicates which reference frame contains the film grain parameters to be used for this frame.
+ /// It is a requirement of bitstream conformance that FilmGrainParamsRefIdx is equal to ref_frame_idx[ j ] for some value
+ /// of j in the range 0 to REFS_PER_FRAME - 1.
+ ///
+ public uint FilmGrainParamsRefidx { get; set; }
+
+ ///
+ /// Gets or sets NumYPoints. Specifies the number of points for the piece-wise linear scaling function of the luma component.
+ /// It is a requirement of bitstream conformance that NumYPoints is less than or equal to 14.
+ ///
+ public uint NumYPoints { get; set; }
+
+ ///
+ /// Gets or sets PointYValue. Represents the x (luma value) coordinate for the i-th point of the piecewise linear scaling function for
+ /// luma component.The values are signaled on the scale of 0..255. (In case of 10 bit video, these values correspond to
+ /// luma values divided by 4. In case of 12 bit video, these values correspond to luma values divided by 16.)
+ ///
+ /// If i is greater than 0, it is a requirement of bitstream conformance that point_y_value[ i ] is greater than point_y_value[ i - 1] (this ensures the x coordinates are specified in increasing order).
+ ///
+ public uint[]? PointYValue { get; set; }
+
+ ///
+ /// Gets or sets PointYScaling. Represents the scaling (output) value for the i-th point of the piecewise linear scaling function for luma component.
+ ///
+ public uint[]? PointYScaling { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the chroma scaling is inferred from the luma scaling.
+ ///
+ public bool ChromaScalingFromLuma { get; set; }
+
+ ///
+ /// Gets or sets NumCbPoints. Specifies the number of points for the piece-wise linear scaling function of the cb component.
+ /// It is a requirement of bitstream conformance that NumCbPoints is less than or equal to 10.
+ ///
+ public uint NumCbPoints { get; set; }
+
+ ///
+ /// Gets or sets NumCrPoints. Specifies represents the number of points for the piece-wise linear scaling function of the cr component.
+ /// It is a requirement of bitstream conformance that NumCrPoints is less than or equal to 10.
+ ///
+ public uint NumCrPoints { get; set; }
+
+ ///
+ /// Gets or sets PointCbValue. Represents the x coordinate for the i-th point of the piece-wise linear scaling function for cb
+ /// component.The values are signaled on the scale of 0..255.
+ /// If i is greater than 0, it is a requirement of bitstream conformance that point_cb_value[ i ] is greater than point_cb_value[ i - 1 ].
+ ///
+ public uint[]? PointCbValue { get; set; }
+
+ ///
+ /// Gets or sets PointCbScaling. Represents the scaling (output) value for the i-th point of the piecewise linear scaling function for cb component.
+ ///
+ public uint[]? PointCbScaling { get; set; }
+
+ ///
+ /// Gets or sets PointCrValue. Represents the x coordinate for the i-th point of the piece-wise linear scaling function for cr component.
+ /// The values are signaled on the scale of 0..255.
+ /// If i is greater than 0, it is a requirement of bitstream conformance that point_cr_value[ i ] is greater than point_cr_value[ i - 1 ].
+ ///
+ public uint[]? PointCrValue { get; set; }
+
+ ///
+ /// Gets or sets PointCrScaling. Represents the scaling (output) value for the i-th point of the piecewise linear scaling function for cr component.
+ ///
+ public uint[]? PointCrScaling { get; set; }
+
+ ///
+ /// Gets or sets GrainScalingMinus8. represents the shift – 8 applied to the values of the chroma component. The
+ /// grain_scaling_minus_8 can take values of 0..3 and determines the range and quantization step of the standard deviation of film grain.
+ ///
+ public uint GrainScalingMinus8 { get; set; }
+
+ ///
+ /// Gets or sets ArCoeffLag. Specifies the number of auto-regressive coefficients for luma and chroma.
+ ///
+ public uint ArCoeffLag { get; set; }
+
+ ///
+ /// Gets or sets ArCoeffsYPlus128. Specifies auto-regressive coefficients used for the Y plane.
+ ///
+ public uint[]? ArCoeffsYPlus128 { get; set; }
+
+ ///
+ /// Gets or sets ArCoeffsCbPlus128. Specifies auto-regressive coefficients used for the U plane.
+ ///
+ public uint[]? ArCoeffsCbPlus128 { get; set; }
+
+ ///
+ /// Gets or sets ArCoeffsCrPlus128. Specifies auto-regressive coefficients used for the V plane.
+ ///
+ public uint[]? ArCoeffsCrPlus128 { get; set; }
+
+ ///
+ /// Gets or sets ArCoeffShiftMinus6. Specifies the range of the auto-regressive coefficients. Values of 0, 1, 2, and 3 correspond to the
+ /// ranges for auto-regressive coefficients of[-2, 2), [-1, 1), [-0.5, 0.5) and [-0.25, 0.25) respectively.
+ ///
+ public uint ArCoeffShiftMinus6 { get; set; }
+
+ ///
+ /// Gets or sets GrainScaleShift. Specifies how much the Gaussian random numbers should be scaled down during the grain synthesis process.
+ ///
+ public uint GrainScaleShift { get; set; }
+
+ ///
+ /// Gets or sets CbMult. Represents a multiplier for the cb component used in derivation of the input index to the cb component scaling function.
+ ///
+ public uint CbMult { get; set; }
+
+ ///
+ /// Gets or sets CbLumaMult. Represents a multiplier for the average luma component used in derivation of the input index to the cb component scaling function.
+ ///
+ public uint CbLumaMult { get; set; }
+
+ ///
+ /// Gets or sets CbOffset. Represents an offset used in derivation of the input index to the cb component scaling function.
+ ///
+ public uint CbOffset { get; set; }
+
+ ///
+ /// Gets or sets CrMult. Represents a multiplier for the cr component used in derivation of the input index to the cr component scaling function.
+ ///
+ public uint CrMult { get; set; }
+
+ ///
+ /// Gets or sets CrLumaMult. Represents a multiplier for the average luma component used in derivation of the input index to the cr component scaling function.
+ ///
+ public uint CrLumaMult { get; set; }
+
+ ///
+ /// Gets or sets CrOffset. Represents an offset used in derivation of the input index to the cr component scaling function.
+ ///
+ public uint CrOffset { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the overlap between film grain blocks shall be applied. OverlapFlag equal to false
+ /// indicates that the overlap between film grain blocks shall not be applied.
+ ///
+ public bool OverlapFlag { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether clipping to the restricted (studio) range shall be applied to the sample
+ /// values after adding the film grain(see the semantics for color_range for an explanation of studio swing).
+ /// ClipToRestrictedRange equal to false indicates that clipping to the full range shall be applied to the sample values after adding the film grain.
+ ///
+ public bool ClipToRestrictedRange { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs
new file mode 100644
index 0000000000..adc759267f
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs
@@ -0,0 +1,98 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuFrameHeader
+{
+ public bool ForceIntegerMotionVector { get; set; }
+
+ public bool AllowIntraBlockCopy { get; set; }
+
+ public bool UseReferenceFrameMotionVectors { get; set; }
+
+ public bool AllowHighPrecisionMotionVector { get; set; }
+
+ public ObuTileGroupHeader TilesInfo { get; set; } = new ObuTileGroupHeader();
+
+ public bool CodedLossless { get; set; }
+
+ public bool[] LosslessArray { get; set; } = new bool[Av1Constants.MaxSegmentCount];
+
+ public ObuQuantizationParameters QuantizationParameters { get; set; } = new ObuQuantizationParameters();
+
+ public ObuSegmentationParameters SegmentationParameters { get; set; } = new ObuSegmentationParameters();
+
+ public bool AllLossless { get; set; }
+
+ public bool AllowWarpedMotion { get; set; }
+
+ public ObuReferenceMode ReferenceMode { get; set; }
+
+ public ObuFilmGrainParameters FilmGrainParameters { get; set; } = new ObuFilmGrainParameters();
+
+ public bool UseReducedTransformSet { get; set; }
+
+ public ObuLoopFilterParameters LoopFilterParameters { get; set; } = new ObuLoopFilterParameters();
+
+ public ObuLoopRestorationParameters LoopRestorationParameters { get; set; } = new ObuLoopRestorationParameters();
+
+ public ObuConstraintDirectionalEnhancementFilterParameters CdefParameters { get; set; } = new ObuConstraintDirectionalEnhancementFilterParameters();
+
+ public int ModeInfoStride { get; set; }
+
+ public bool DisableFrameEndUpdateCdf { get; set; }
+
+ public ObuSkipModeParameters SkipModeParameters { get; set; } = new ObuSkipModeParameters();
+
+ public Av1TransformMode TransformMode { get; set; }
+
+ public ObuDeltaParameters DeltaLoopFilterParameters { get; set; } = new ObuDeltaParameters();
+
+ public ObuDeltaParameters DeltaQParameters { get; set; } = new ObuDeltaParameters();
+
+ public bool IsIntra => this.FrameType is ObuFrameType.IntraOnlyFrame or ObuFrameType.KeyFrame;
+
+ internal ObuFrameSize FrameSize { get; set; } = new ObuFrameSize();
+
+ internal int ModeInfoColumnCount { get; set; }
+
+ internal int ModeInfoRowCount { get; set; }
+
+ internal bool ShowExistingFrame { get; set; }
+
+ internal ObuFrameType FrameType { get; set; }
+
+ internal bool[] ReferenceValid { get; set; } = new bool[Av1Constants.ReferenceFrameCount];
+
+ internal bool[] ReferenceOrderHint { get; set; } = new bool[Av1Constants.ReferenceFrameCount];
+
+ internal bool ShowFrame { get; set; }
+
+ internal bool ShowableFrame { get; set; }
+
+ internal uint FrameToShowMapIdx { get; set; }
+
+ internal uint DisplayFrameId { get; set; }
+
+ internal bool ErrorResilientMode { get; set; }
+
+ internal bool AllowScreenContentTools { get; set; }
+
+ internal bool DisableCdfUpdate { get; set; }
+
+ internal uint CurrentFrameId { get; set; }
+
+ internal uint[] ReferenceFrameIndex { get; set; } = new uint[Av1Constants.ReferenceFrameCount];
+
+ internal uint OrderHint { get; set; }
+
+ internal uint PrimaryReferenceFrame { get; set; } = Av1Constants.PrimaryReferenceFrameNone;
+
+ internal uint RefreshFrameFlags { get; set; }
+
+ // 5.9.31. Temporal point info syntax
+ internal uint FramePresentationTime { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs
new file mode 100644
index 0000000000..7075b50b82
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuFrameSize
+{
+ internal int FrameWidth { get; set; }
+
+ internal int FrameHeight { get; set; }
+
+ internal int SuperResolutionDenominator { get; set; }
+
+ internal int SuperResolutionUpscaledWidth { get; set; }
+
+ internal int RenderWidth { get; set; }
+
+ internal int RenderHeight { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs
new file mode 100644
index 0000000000..eb2414edc7
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal enum ObuFrameType
+{
+ KeyFrame = 0,
+ InterFrame = 1,
+ IntraOnlyFrame = 2,
+ SwitchFrame = 3,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs
new file mode 100644
index 0000000000..f55d3eb501
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuHeader
+{
+ public int Size { get; set; }
+
+ public ObuType Type { get; set; }
+
+ public bool HasSize { get; set; }
+
+ public bool HasExtension { get; set; }
+
+ public int TemporalId { get; set; }
+
+ public int SpatialId { get; set; }
+
+ public int PayloadSize { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs
new file mode 100644
index 0000000000..ad32d0c01d
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuLoopFilterParameters
+{
+ public ObuLoopFilterParameters()
+ {
+ this.ReferenceDeltas = [1, 0, 0, 0, 0, -1, -1, -1];
+ this.ModeDeltas = [0, 0];
+ }
+
+ public int[] FilterLevel { get; internal set; } = new int[2];
+
+ public int FilterLevelU { get; internal set; }
+
+ public int FilterLevelV { get; internal set; }
+
+ public int SharpnessLevel { get; internal set; }
+
+ public bool ReferenceDeltaModeEnabled { get; internal set; }
+
+ public bool ReferenceDeltaModeUpdate { get; internal set; }
+
+ public int[] ReferenceDeltas { get; }
+
+ public int[] ModeDeltas { get; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationItem.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationItem.cs
new file mode 100644
index 0000000000..ade31a6606
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationItem.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuLoopRestorationItem
+{
+ internal int Size { get; set; }
+
+ internal ObuRestorationType Type { get; set; } = ObuRestorationType.None;
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs
new file mode 100644
index 0000000000..18db716813
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuLoopRestorationParameters
+{
+ internal ObuLoopRestorationParameters()
+ {
+ this.Items = new ObuLoopRestorationItem[3];
+ this.Items[0] = new();
+ this.Items[1] = new();
+ this.Items[2] = new();
+ }
+
+ internal bool UsesLoopRestoration { get; set; }
+
+ internal bool UsesChromaLoopRestoration { get; set; }
+
+ internal ObuLoopRestorationItem[] Items { get; }
+
+ internal int UnitShift { get; set; }
+
+ internal int UVShift { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs
new file mode 100644
index 0000000000..e9658997fb
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal enum ObuMatrixCoefficients
+{
+ Identity = 0,
+ Bt407 = 1,
+ Unspecified = 2,
+ Fcc = 4,
+ Bt470BG = 5,
+ Bt601 = 6,
+ Smpte240 = 7,
+ SmpteYCgCo = 8,
+ Bt2020NonConstantLuminance = 9,
+ Bt2020ConstantLuminance = 10,
+ Smpte2085 = 11,
+ ChromaticityDerivedNonConstantLuminance = 12,
+ ChromaticityDerivedConstandLuminance = 13,
+ Bt2100ICtCp = 14,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs
new file mode 100644
index 0000000000..d6788f1745
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal enum ObuMetadataType
+{
+ ItutT35,
+ HdrCll,
+ HdrMdcv,
+ Scalability,
+ Timecode
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs
new file mode 100644
index 0000000000..a398124e39
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuOperatingPoint
+{
+ internal int OperatorIndex { get; set; }
+
+ internal int SequenceLevelIndex { get; set; }
+
+ internal int SequenceTier { get; set; }
+
+ internal bool IsDecoderModelInfoPresent { get; set; }
+
+ internal bool IsInitialDisplayDelayPresent { get; set; }
+
+ internal uint InitialDisplayDelay { get; set; }
+
+ ///
+ /// Gets or sets of sets the Idc bitmask. The bitmask that indicates which spatial and temporal layers should be decoded for
+ /// operating point i.Bit k is equal to 1 if temporal layer k should be decoded(for k between 0 and 7). Bit j+8 is equal to 1 if
+ /// spatial layer j should be decoded(for j between 0 and 3).
+ /// However, if operating_point_idc[i] is equal to 0 then the coded video sequence has no scalability information in OBU
+ /// extension headers and the operating point applies to the entire coded video sequence.This means that all OBUs must be decoded.
+ /// It is a requirement of bitstream conformance that operating_point_idc[i] is not equal to operating_point_idc[j] for j = 0..(i- 1).
+ ///
+ internal uint Idc { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs
new file mode 100644
index 0000000000..f540a297fa
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuOrderHintInfo
+{
+ public bool EnableOrderHint { get; internal set; }
+
+ internal bool EnableJointCompound { get; set; }
+
+ internal bool EnableReferenceFrameMotionVectors { get; set; }
+
+ internal int OrderHintBits { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs
new file mode 100644
index 0000000000..a3924eeeec
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuQuantizationParameters
+{
+ public int BaseQIndex { get; set; }
+
+ public int[] QIndex { get; set; } = new int[Av1Constants.MaxSegmentCount];
+
+ public bool IsUsingQMatrix { get; internal set; }
+
+ public int[] DeltaQDc { get; internal set; } = new int[3];
+
+ public int[] DeltaQAc { get; internal set; } = new int[3];
+
+ public int[] QMatrix { get; internal set; } = new int[3];
+
+ public bool HasSeparateUvDelta { get; internal set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
new file mode 100644
index 0000000000..b4054bfcf4
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
@@ -0,0 +1,1814 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+///
+/// Reader for Open Bitstream Units (OBU's).
+///
+internal class ObuReader
+{
+ ///
+ /// Maximum value used for loop filtering.
+ ///
+ private const int MaxLoopFilter = 63;
+
+ ///
+ /// Number of segments allowed in segmentation map.
+ ///
+ private const int MaxSegments = 0;
+
+ ///
+ /// Number of segment features.
+ ///
+ private const int SegLvlMax = 8;
+
+ ///
+ /// Index for reference frame segment feature.
+ ///
+ private const int SegLvlRefFrame = 5;
+
+ private const int PrimaryRefNone = 7;
+
+ private static readonly int[] SegmentationFeatureBits = [8, 6, 6, 6, 6, 3, 0, 0];
+
+ private static readonly int[] SegmentationFeatureSigned = [1, 1, 1, 1, 1, 0, 0, 0];
+
+ private static readonly int[] SegmentationFeatureMax = [255, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, 7, 0, 0];
+
+ public ObuSequenceHeader? SequenceHeader { get; set; }
+
+ public ObuFrameHeader? FrameHeader { get; set; }
+
+ ///
+ /// Decode all OBU's in a frame.
+ ///
+ public void ReadAll(ref Av1BitStreamReader reader, int dataSize, IAv1TileReader decoder, bool isAnnexB = false)
+ {
+ bool seenFrameHeader = false;
+ bool frameDecodingFinished = false;
+ while (!frameDecodingFinished)
+ {
+ int lengthSize = 0;
+ int payloadSize = 0;
+ if (isAnnexB)
+ {
+ ReadObuSize(ref reader, out payloadSize, out lengthSize);
+ }
+
+ ObuHeader header = ReadObuHeaderSize(ref reader, out lengthSize);
+ if (isAnnexB)
+ {
+ header.PayloadSize -= header.Size;
+ dataSize -= lengthSize;
+ lengthSize = 0;
+ }
+
+ payloadSize = header.PayloadSize;
+ dataSize -= header.Size + lengthSize;
+ if (isAnnexB && dataSize < payloadSize)
+ {
+ throw new InvalidImageContentException("Corrupt frame");
+ }
+
+ switch (header.Type)
+ {
+ case ObuType.SequenceHeader:
+ this.SequenceHeader = new();
+ ReadSequenceHeader(ref reader, this.SequenceHeader);
+ if (this.SequenceHeader.ColorConfig.BitDepth == Av1BitDepth.TwelveBit)
+ {
+ // TODO: Initialize 12 bit predictors
+ }
+
+ break;
+ case ObuType.FrameHeader:
+ case ObuType.RedundantFrameHeader:
+ case ObuType.Frame:
+ if (header.Type != ObuType.Frame)
+ {
+ // Nothing to do here.
+ }
+ else if (header.Type != ObuType.FrameHeader)
+ {
+ Guard.IsFalse(seenFrameHeader, nameof(seenFrameHeader), "Frame header expected");
+ }
+ else
+ {
+ Guard.IsTrue(seenFrameHeader, nameof(seenFrameHeader), "Already decoded a frame header");
+ }
+
+ if (!seenFrameHeader)
+ {
+ seenFrameHeader = true;
+ this.FrameHeader = new();
+ this.ReadFrameHeader(ref reader, header, header.Type != ObuType.Frame);
+ }
+
+ if (header.Type != ObuType.Frame)
+ {
+ break; // For OBU_TILE_GROUP comes under OBU_FRAME
+ }
+
+ goto TILE_GROUP;
+ case ObuType.TileGroup:
+ TILE_GROUP:
+ if (!seenFrameHeader)
+ {
+ throw new InvalidImageContentException("Corrupt frame");
+ }
+
+ this.ReadTileGroup(ref reader, decoder, header, out frameDecodingFinished);
+ if (frameDecodingFinished)
+ {
+ seenFrameHeader = false;
+ }
+
+ break;
+ case ObuType.TemporalDelimiter:
+ // 5.6. Temporal delimiter obu syntax.
+ seenFrameHeader = false;
+ break;
+ default:
+ // Ignore unknown OBU types.
+ // throw new InvalidImageContentException($"Unknown OBU header found: {header.Type.ToString()}");
+ break;
+ }
+
+ dataSize -= payloadSize;
+ if (dataSize <= 0)
+ {
+ frameDecodingFinished = true;
+ }
+ }
+ }
+
+ ///
+ /// 5.3.2. OBU header syntax.
+ ///
+ private static ObuHeader ReadObuHeader(ref Av1BitStreamReader reader)
+ {
+ ObuHeader header = new();
+ if (reader.ReadBoolean())
+ {
+ throw new ImageFormatException("Forbidden bit in header should be unset.");
+ }
+
+ header.Size = 1;
+ header.Type = (ObuType)reader.ReadLiteral(4);
+ header.HasExtension = reader.ReadBoolean();
+ header.HasSize = reader.ReadBoolean();
+ if (reader.ReadBoolean())
+ {
+ throw new ImageFormatException("Reserved bit in header should be unset.");
+ }
+
+ if (header.HasExtension)
+ {
+ header.Size++;
+ header.TemporalId = (int)reader.ReadLiteral(3);
+ header.SpatialId = (int)reader.ReadLiteral(2);
+ if (reader.ReadLiteral(3) != 0u)
+ {
+ throw new ImageFormatException("Reserved bits in header extension should be unset.");
+ }
+ }
+ else
+ {
+ header.SpatialId = 0;
+ header.TemporalId = 0;
+ }
+
+ return header;
+ }
+
+ private static void ReadObuSize(ref Av1BitStreamReader reader, out int obuSize, out int lengthSize)
+ {
+ ulong rawSize = reader.ReadLittleEndianBytes128(out lengthSize);
+ if (rawSize > uint.MaxValue)
+ {
+ throw new ImageFormatException("OBU block too large.");
+ }
+
+ obuSize = (int)rawSize;
+ }
+
+ ///
+ /// Read OBU header and size.
+ ///
+ private static ObuHeader ReadObuHeaderSize(ref Av1BitStreamReader reader, out int lengthSize)
+ {
+ ObuHeader header = ReadObuHeader(ref reader);
+ lengthSize = 0;
+ if (header.HasSize)
+ {
+ ReadObuSize(ref reader, out int payloadSize, out lengthSize);
+ header.PayloadSize = payloadSize;
+ }
+
+ return header;
+ }
+
+ ///
+ /// Check that the trailing bits start with a 1 and end with 0s.
+ ///
+ /// Consumes a byte, if already byte aligned before the check.
+ private static void ReadTrailingBits(ref Av1BitStreamReader reader)
+ {
+ int bitsBeforeAlignment = 8 - (reader.BitPosition & 0x7);
+ uint trailing = reader.ReadLiteral(bitsBeforeAlignment);
+ if (trailing != (1U << (bitsBeforeAlignment - 1)))
+ {
+ throw new ImageFormatException("Trailing bits not properly formatted.");
+ }
+ }
+
+ ///
+ /// 5.3.5. Byte alignment syntax.
+ ///
+ private static void AlignToByteBoundary(ref Av1BitStreamReader reader)
+ {
+ while ((reader.BitPosition & 0x7) > 0)
+ {
+ if (reader.ReadBoolean())
+ {
+ throw new ImageFormatException("Incorrect byte alignment padding bits.");
+ }
+ }
+ }
+
+ private void ComputeImageSize(ObuSequenceHeader sequenceHeader)
+ {
+ ObuFrameHeader frameHeader = this.FrameHeader!;
+ frameHeader.ModeInfoColumnCount = 2 * ((frameHeader.FrameSize.FrameWidth + 7) >> 3);
+ frameHeader.ModeInfoRowCount = 2 * ((frameHeader.FrameSize.FrameHeight + 7) >> 3);
+ frameHeader.ModeInfoStride = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, Av1Constants.MaxSuperBlockSizeLog2) >> Av1Constants.ModeInfoSizeLog2;
+ }
+
+ ///
+ /// 5.5.1. General sequence header OBU syntax.
+ ///
+ internal static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
+ {
+ sequenceHeader.SequenceProfile = (ObuSequenceProfile)reader.ReadLiteral(3);
+ if (sequenceHeader.SequenceProfile > Av1Constants.MaxSequenceProfile)
+ {
+ throw new ImageFormatException("Unknown sequence profile.");
+ }
+
+ sequenceHeader.IsStillPicture = reader.ReadBoolean();
+ sequenceHeader.IsReducedStillPictureHeader = reader.ReadBoolean();
+ if (sequenceHeader.IsReducedStillPictureHeader)
+ {
+ sequenceHeader.TimingInfo = null;
+ sequenceHeader.DecoderModelInfoPresentFlag = false;
+ sequenceHeader.InitialDisplayDelayPresentFlag = false;
+ sequenceHeader.OperatingPoint = new ObuOperatingPoint[1];
+ ObuOperatingPoint operatingPoint = new();
+ sequenceHeader.OperatingPoint[0] = operatingPoint;
+ operatingPoint.OperatorIndex = 0;
+ operatingPoint.SequenceLevelIndex = (int)reader.ReadLiteral(Av1Constants.LevelBits);
+ if (!IsValidSequenceLevel(sequenceHeader.OperatingPoint[0].SequenceLevelIndex))
+ {
+ throw new ImageFormatException("Invalid sequence level.");
+ }
+
+ operatingPoint.SequenceTier = 0;
+ operatingPoint.IsDecoderModelInfoPresent = false;
+ operatingPoint.IsInitialDisplayDelayPresent = false;
+ }
+ else
+ {
+ sequenceHeader.TimingInfoPresentFlag = reader.ReadBoolean();
+ if (sequenceHeader.TimingInfoPresentFlag)
+ {
+ ReadTimingInfo(ref reader, sequenceHeader);
+ sequenceHeader.DecoderModelInfoPresentFlag = reader.ReadBoolean();
+ if (sequenceHeader.DecoderModelInfoPresentFlag)
+ {
+ ReadDecoderModelInfo(ref reader, sequenceHeader);
+ }
+ else
+ {
+ sequenceHeader.DecoderModelInfoPresentFlag = false;
+ }
+ }
+
+ sequenceHeader.InitialDisplayDelayPresentFlag = reader.ReadBoolean();
+ uint operatingPointsCnt = reader.ReadLiteral(5) + 1;
+ sequenceHeader.OperatingPoint = new ObuOperatingPoint[operatingPointsCnt];
+ for (int i = 0; i < operatingPointsCnt; i++)
+ {
+ sequenceHeader.OperatingPoint[i] = new ObuOperatingPoint
+ {
+ Idc = reader.ReadLiteral(12),
+ SequenceLevelIndex = (int)reader.ReadLiteral(5)
+ };
+ if (sequenceHeader.OperatingPoint[i].SequenceLevelIndex > 7)
+ {
+ sequenceHeader.OperatingPoint[i].SequenceTier = (int)reader.ReadLiteral(1);
+ }
+ else
+ {
+ sequenceHeader.OperatingPoint[i].SequenceTier = 0;
+ }
+
+ if (sequenceHeader.DecoderModelInfoPresentFlag)
+ {
+ sequenceHeader.OperatingPoint[i].IsDecoderModelInfoPresent = reader.ReadBoolean();
+ if (sequenceHeader.OperatingPoint[i].IsDecoderModelInfoPresent)
+ {
+ // TODO: operating_parameters_info( i )
+ }
+ }
+ else
+ {
+ sequenceHeader.OperatingPoint[i].IsDecoderModelInfoPresent = false;
+ }
+
+ if (sequenceHeader.InitialDisplayDelayPresentFlag)
+ {
+ sequenceHeader.OperatingPoint[i].IsInitialDisplayDelayPresent = reader.ReadBoolean();
+ if (sequenceHeader.OperatingPoint[i].IsInitialDisplayDelayPresent)
+ {
+ sequenceHeader.OperatingPoint[i].InitialDisplayDelay = reader.ReadLiteral(4) + 1;
+ }
+ }
+ }
+ }
+
+ // Video related flags removed
+
+ // SVT-TODO: int operatingPoint = this.ChooseOperatingPoint();
+ // sequenceHeader.OperatingPointIndex = (int)operatingPointIndices[operatingPoint];
+ sequenceHeader.FrameWidthBits = (int)reader.ReadLiteral(4) + 1;
+ sequenceHeader.FrameHeightBits = (int)reader.ReadLiteral(4) + 1;
+ sequenceHeader.MaxFrameWidth = (int)reader.ReadLiteral(sequenceHeader.FrameWidthBits) + 1;
+ sequenceHeader.MaxFrameHeight = (int)reader.ReadLiteral(sequenceHeader.FrameHeightBits) + 1;
+ if (sequenceHeader.IsReducedStillPictureHeader)
+ {
+ sequenceHeader.IsFrameIdNumbersPresent = false;
+ }
+ else
+ {
+ sequenceHeader.IsFrameIdNumbersPresent = reader.ReadBoolean();
+ }
+
+ if (sequenceHeader.IsFrameIdNumbersPresent)
+ {
+ sequenceHeader.DeltaFrameIdLength = (int)reader.ReadLiteral(4) + 2;
+ sequenceHeader.AdditionalFrameIdLength = reader.ReadLiteral(3) + 1;
+ }
+
+ // Video related flags removed
+ sequenceHeader.Use128x128Superblock = reader.ReadBoolean();
+ sequenceHeader.EnableFilterIntra = reader.ReadBoolean();
+ sequenceHeader.EnableIntraEdgeFilter = reader.ReadBoolean();
+
+ if (sequenceHeader.IsReducedStillPictureHeader)
+ {
+ sequenceHeader.EnableInterIntraCompound = false;
+ sequenceHeader.EnableMaskedCompound = false;
+ sequenceHeader.EnableWarpedMotion = false;
+ sequenceHeader.EnableDualFilter = false;
+ sequenceHeader.OrderHintInfo.EnableJointCompound = false;
+ sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors = false;
+ sequenceHeader.ForceScreenContentTools = 2; // SELECT_SCREEN_CONTENT_TOOLS
+ sequenceHeader.ForceIntegerMotionVector = 2; // SELECT_INTEGER_MV
+ sequenceHeader.OrderHintInfo.OrderHintBits = 0;
+ }
+ else
+ {
+ sequenceHeader.EnableInterIntraCompound = reader.ReadBoolean();
+ sequenceHeader.EnableMaskedCompound = reader.ReadBoolean();
+ sequenceHeader.EnableWarpedMotion = reader.ReadBoolean();
+ sequenceHeader.EnableDualFilter |= reader.ReadBoolean();
+ sequenceHeader.EnableOrderHint = reader.ReadBoolean();
+ if (sequenceHeader.EnableOrderHint)
+ {
+ sequenceHeader.OrderHintInfo.EnableJointCompound = reader.ReadBoolean();
+ sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors = reader.ReadBoolean();
+ }
+ else
+ {
+ sequenceHeader.OrderHintInfo.EnableJointCompound = false;
+ sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors = false;
+ }
+
+ bool seqChooseScreenContentTools = reader.ReadBoolean();
+ if (seqChooseScreenContentTools)
+ {
+ sequenceHeader.ForceScreenContentTools = 2; // SELECT_SCREEN_CONTENT_TOOLS
+ }
+ else
+ {
+ sequenceHeader.ForceScreenContentTools = (int)reader.ReadLiteral(1);
+ }
+
+ if (sequenceHeader.ForceScreenContentTools > 0)
+ {
+ bool seqChooseIntegerMv = reader.ReadBoolean();
+ if (seqChooseIntegerMv)
+ {
+ sequenceHeader.ForceIntegerMotionVector = 2; // SELECT_INTEGER_MV
+ }
+ else
+ {
+ sequenceHeader.ForceIntegerMotionVector = (int)reader.ReadLiteral(1);
+ }
+ }
+ else
+ {
+ sequenceHeader.ForceIntegerMotionVector = 2; // SELECT_INTEGER_MV
+ }
+
+ if (sequenceHeader.EnableOrderHint)
+ {
+ sequenceHeader.OrderHintInfo.OrderHintBits = (int)reader.ReadLiteral(3) + 1;
+ }
+ else
+ {
+ sequenceHeader.OrderHintInfo.OrderHintBits = 0;
+ }
+ }
+
+ // Video related flags removed
+ sequenceHeader.EnableSuperResolution = reader.ReadBoolean();
+ sequenceHeader.EnableCdef = reader.ReadBoolean();
+ sequenceHeader.EnableRestoration = reader.ReadBoolean();
+ sequenceHeader.ColorConfig = ReadColorConfig(ref reader, sequenceHeader);
+ sequenceHeader.AreFilmGrainingParametersPresent = reader.ReadBoolean();
+ ReadTrailingBits(ref reader);
+ }
+
+ ///
+ /// 5.5.2. Color config syntax.
+ ///
+ private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
+ {
+ ObuColorConfig colorConfig = new();
+ ReadBitDepth(ref reader, colorConfig, sequenceHeader);
+ colorConfig.IsMonochrome = false;
+ if (sequenceHeader.SequenceProfile != ObuSequenceProfile.High)
+ {
+ colorConfig.IsMonochrome = reader.ReadBoolean();
+ }
+
+ colorConfig.IsColorDescriptionPresent = reader.ReadBoolean();
+ colorConfig.ColorPrimaries = ObuColorPrimaries.Unspecified;
+ colorConfig.TransferCharacteristics = ObuTransferCharacteristics.Unspecified;
+ colorConfig.MatrixCoefficients = ObuMatrixCoefficients.Unspecified;
+ if (colorConfig.IsColorDescriptionPresent)
+ {
+ colorConfig.ColorPrimaries = (ObuColorPrimaries)reader.ReadLiteral(8);
+ colorConfig.TransferCharacteristics = (ObuTransferCharacteristics)reader.ReadLiteral(8);
+ colorConfig.MatrixCoefficients = (ObuMatrixCoefficients)reader.ReadLiteral(8);
+ }
+
+ colorConfig.ColorRange = false;
+ colorConfig.SubSamplingX = false;
+ colorConfig.SubSamplingY = false;
+ colorConfig.ChromaSamplePosition = ObuChromoSamplePosition.Unknown;
+ colorConfig.HasSeparateUvDelta = false;
+ if (colorConfig.IsMonochrome)
+ {
+ colorConfig.ColorRange = reader.ReadBoolean();
+ colorConfig.SubSamplingX = true;
+ colorConfig.SubSamplingY = true;
+ return colorConfig;
+ }
+ else if (
+ colorConfig.ColorPrimaries == ObuColorPrimaries.Bt709 &&
+ colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb &&
+ colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity)
+ {
+ colorConfig.ColorRange = true;
+ colorConfig.SubSamplingX = false;
+ colorConfig.SubSamplingY = false;
+ }
+ else
+ {
+ colorConfig.ColorRange = reader.ReadBoolean();
+ switch (sequenceHeader.SequenceProfile)
+ {
+ case ObuSequenceProfile.Main:
+ colorConfig.SubSamplingX = true;
+ colorConfig.SubSamplingY = true;
+ break;
+ case ObuSequenceProfile.High:
+ colorConfig.SubSamplingX = false;
+ colorConfig.SubSamplingY = false;
+ break;
+ case ObuSequenceProfile.Professional:
+ default:
+ if (colorConfig.BitDepth == Av1BitDepth.TwelveBit)
+ {
+ colorConfig.SubSamplingX = reader.ReadBoolean();
+ if (colorConfig.SubSamplingX)
+ {
+ colorConfig.SubSamplingY = reader.ReadBoolean();
+ }
+ }
+ else
+ {
+ colorConfig.SubSamplingX = true;
+ colorConfig.SubSamplingY = false;
+ }
+
+ break;
+ }
+
+ if (colorConfig.SubSamplingX && colorConfig.SubSamplingY)
+ {
+ colorConfig.ChromaSamplePosition = (ObuChromoSamplePosition)reader.ReadLiteral(2);
+ }
+ }
+
+ colorConfig.HasSeparateUvDelta = reader.ReadBoolean();
+ return colorConfig;
+ }
+
+ ///
+ /// 5.5.4. Decoder model info syntax.
+ ///
+ private static void ReadDecoderModelInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) => sequenceHeader.DecoderModelInfo = new ObuDecoderModelInfo
+ {
+ BufferDelayLength = reader.ReadLiteral(5) + 1,
+ NumUnitsInDecodingTick = reader.ReadLiteral(16),
+ BufferRemovalTimeLength = reader.ReadLiteral(5) + 1,
+ FramePresentationTimeLength = reader.ReadLiteral(5) + 1
+ };
+
+ ///
+ /// 5.5.3. Timing info syntax.
+ ///
+ private static void ReadTimingInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
+ {
+ sequenceHeader.TimingInfo = new ObuTimingInfo
+ {
+ NumUnitsInDisplayTick = reader.ReadLiteral(32),
+ TimeScale = reader.ReadLiteral(32),
+ EqualPictureInterval = reader.ReadBoolean()
+ };
+
+ if (sequenceHeader.TimingInfo.EqualPictureInterval)
+ {
+ sequenceHeader.TimingInfo.NumTicksPerPicture = reader.ReadUnsignedVariableLength() + 1;
+ }
+ }
+
+ private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader)
+ {
+ bool hasHighBitDepth = reader.ReadBoolean();
+ if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && hasHighBitDepth)
+ {
+ colorConfig.BitDepth = reader.ReadBoolean() ? Av1BitDepth.TwelveBit : Av1BitDepth.TenBit;
+ }
+ else if (sequenceHeader.SequenceProfile <= ObuSequenceProfile.Professional)
+ {
+ colorConfig.BitDepth = hasHighBitDepth ? Av1BitDepth.TenBit : Av1BitDepth.EightBit;
+ }
+ else
+ {
+ colorConfig.BitDepth = Av1BitDepth.EightBit;
+ }
+ }
+
+ ///
+ /// 5.9.8. Superres params syntax.
+ ///
+ private void ReadSuperResolutionParameters(ref Av1BitStreamReader reader)
+ {
+ ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
+ ObuFrameHeader frameHeader = this.FrameHeader!;
+ bool useSuperResolution = false;
+ if (sequenceHeader.EnableSuperResolution)
+ {
+ useSuperResolution = reader.ReadBoolean();
+ }
+
+ if (useSuperResolution)
+ {
+ frameHeader.FrameSize.SuperResolutionDenominator = (int)reader.ReadLiteral(Av1Constants.SuperResolutionScaleBits) + Av1Constants.SuperResolutionScaleDenominatorMinimum;
+ }
+ else
+ {
+ frameHeader.FrameSize.SuperResolutionDenominator = Av1Constants.ScaleNumerator;
+ }
+
+ frameHeader.FrameSize.SuperResolutionUpscaledWidth = frameHeader.FrameSize.FrameWidth;
+ frameHeader.FrameSize.FrameWidth =
+ ((frameHeader.FrameSize.SuperResolutionUpscaledWidth * Av1Constants.ScaleNumerator) +
+ (frameHeader.FrameSize.SuperResolutionDenominator / 2)) /
+ frameHeader.FrameSize.SuperResolutionDenominator;
+
+ /*
+ if (frameHeader.FrameSize.SuperResolutionDenominator != Av1Constants.ScaleNumerator)
+ {
+ int manWidth = Math.Min(16, frameHeader.FrameSize.SuperResolutionUpscaledWidth);
+ frameHeader.FrameSize.FrameWidth = Math.Max(manWidth, frameHeader.FrameSize.FrameWidth);
+ }
+ */
+ }
+
+ ///
+ /// 5.9.6. Render size syntax.
+ ///
+ private void ReadRenderSize(ref Av1BitStreamReader reader)
+ {
+ ObuFrameHeader frameHeader = this.FrameHeader!;
+ bool renderSizeAndFrameSizeDifferent = reader.ReadBoolean();
+ if (renderSizeAndFrameSizeDifferent)
+ {
+ frameHeader.FrameSize.RenderWidth = (int)reader.ReadLiteral(16) + 1;
+ frameHeader.FrameSize.RenderHeight = (int)reader.ReadLiteral(16) + 1;
+ }
+ else
+ {
+ frameHeader.FrameSize.RenderWidth = frameHeader.FrameSize.SuperResolutionUpscaledWidth;
+ frameHeader.FrameSize.RenderHeight = frameHeader.FrameSize.FrameHeight;
+ }
+ }
+
+ ///
+ /// 5.9.5. Frame size syntax.
+ ///
+ private void ReadFrameSize(ref Av1BitStreamReader reader, bool frameSizeOverrideFlag)
+ {
+ ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
+ ObuFrameHeader frameHeader = this.FrameHeader!;
+ if (frameSizeOverrideFlag)
+ {
+ frameHeader.FrameSize.FrameWidth = (int)reader.ReadLiteral(sequenceHeader.FrameWidthBits) + 1;
+ frameHeader.FrameSize.FrameHeight = (int)reader.ReadLiteral(sequenceHeader.FrameHeightBits) + 1;
+ }
+ else
+ {
+ frameHeader.FrameSize.FrameWidth = sequenceHeader.MaxFrameWidth;
+ frameHeader.FrameSize.FrameHeight = sequenceHeader.MaxFrameHeight;
+ }
+
+ this.ReadSuperResolutionParameters(ref reader);
+ this.ComputeImageSize(sequenceHeader);
+ }
+
+ ///
+ /// 5.9.15. Tile info syntax.
+ ///
+ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ ObuTileGroupHeader tileInfo = new();
+ int superblockColumnCount;
+ int superblockRowCount;
+ int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2;
+ int superblockShift = superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2;
+ superblockColumnCount = (frameHeader.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift;
+ superblockRowCount = (frameHeader.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift;
+
+ int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (superblockSizeLog2 << 1);
+
+ tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superblockSizeLog2;
+ tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superblockSizeLog2;
+ tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperblock, superblockColumnCount);
+ tileInfo.MaxLog2TileColumnCount = (int)Av1Math.CeilLog2((uint)Math.Min(superblockColumnCount, Av1Constants.MaxTileColumnCount));
+ tileInfo.MaxLog2TileRowCount = (int)Av1Math.CeilLog2((uint)Math.Min(superblockRowCount, Av1Constants.MaxTileRowCount));
+ tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, TileLog2(maxTileAreaOfSuperBlock, superblockColumnCount * superblockRowCount));
+ tileInfo.HasUniformTileSpacing = reader.ReadBoolean();
+ if (tileInfo.HasUniformTileSpacing)
+ {
+ tileInfo.TileColumnCountLog2 = tileInfo.MinLog2TileColumnCount;
+ while (tileInfo.TileColumnCountLog2 < tileInfo.MaxLog2TileColumnCount)
+ {
+ if (reader.ReadBoolean())
+ {
+ tileInfo.TileColumnCountLog2++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ int tileWidthSuperblock = Av1Math.DivideLog2Ceiling(superblockColumnCount, tileInfo.TileColumnCountLog2);
+ DebugGuard.MustBeLessThanOrEqualTo(tileWidthSuperblock, tileInfo.MaxTileWidthSuperblock, nameof(tileWidthSuperblock));
+ int i = 0;
+ tileInfo.TileColumnStartModeInfo = new int[superblockColumnCount + 1];
+ for (int startSuperblock = 0; startSuperblock < superblockColumnCount; startSuperblock += tileWidthSuperblock)
+ {
+ tileInfo.TileColumnStartModeInfo[i] = startSuperblock << superblockShift;
+ i++;
+ }
+
+ tileInfo.TileColumnStartModeInfo[i] = frameHeader.ModeInfoColumnCount;
+ tileInfo.TileColumnCount = i;
+
+ tileInfo.MinLog2TileRowCount = Math.Max(tileInfo.MinLog2TileCount - tileInfo.TileColumnCountLog2, 0);
+ tileInfo.TileRowCountLog2 = tileInfo.MinLog2TileRowCount;
+ while (tileInfo.TileRowCountLog2 < tileInfo.MaxLog2TileRowCount)
+ {
+ if (reader.ReadBoolean())
+ {
+ tileInfo.TileRowCountLog2++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ int tileHeightSuperblock = Av1Math.DivideLog2Ceiling(superblockRowCount, tileInfo.TileRowCountLog2);
+ DebugGuard.MustBeLessThanOrEqualTo(tileHeightSuperblock, tileInfo.MaxTileHeightSuperblock, nameof(tileHeightSuperblock));
+ i = 0;
+ tileInfo.TileRowStartModeInfo = new int[superblockRowCount + 1];
+ for (int startSuperblock = 0; startSuperblock < superblockRowCount; startSuperblock += tileHeightSuperblock)
+ {
+ tileInfo.TileRowStartModeInfo[i] = startSuperblock << superblockShift;
+ i++;
+ }
+
+ tileInfo.TileRowStartModeInfo[i] = frameHeader.ModeInfoRowCount;
+ tileInfo.TileRowCount = i;
+ }
+ else
+ {
+ uint widestTileSuperBlock = 0U;
+ int startSuperBlock = 0;
+ int i = 0;
+ for (; startSuperBlock < superblockColumnCount; i++)
+ {
+ tileInfo.TileColumnStartModeInfo[i] = startSuperBlock << superblockShift;
+ uint maxWidth = (uint)Math.Min(superblockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperblock);
+ uint widthInSuperBlocks = reader.ReadNonSymmetric(maxWidth) + 1;
+ widestTileSuperBlock = Math.Max(widthInSuperBlocks, widestTileSuperBlock);
+ startSuperBlock += (int)widthInSuperBlocks;
+ }
+
+ if (startSuperBlock != superblockColumnCount)
+ {
+ throw new ImageFormatException("Super block tiles width does not add up to total width.");
+ }
+
+ tileInfo.TileColumnStartModeInfo[i] = frameHeader.ModeInfoColumnCount;
+ tileInfo.TileColumnCount = i;
+ tileInfo.TileColumnCountLog2 = TileLog2(1, tileInfo.TileColumnCount);
+ if (tileInfo.MinLog2TileCount > 0)
+ {
+ maxTileAreaOfSuperBlock = (superblockRowCount * superblockColumnCount) >> (tileInfo.MinLog2TileCount + 1);
+ }
+ else
+ {
+ maxTileAreaOfSuperBlock = superblockRowCount * superblockColumnCount;
+ }
+
+ DebugGuard.MustBeGreaterThan(widestTileSuperBlock, 0U, nameof(widestTileSuperBlock));
+ tileInfo.MaxTileHeightSuperblock = Math.Max(maxTileAreaOfSuperBlock / (int)widestTileSuperBlock, 1);
+
+ startSuperBlock = 0;
+ for (i = 0; startSuperBlock < superblockRowCount; i++)
+ {
+ tileInfo.TileRowStartModeInfo[i] = startSuperBlock << superblockShift;
+ uint maxHeight = (uint)Math.Min(superblockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperblock);
+ uint heightInSuperBlocks = reader.ReadNonSymmetric(maxHeight) + 1;
+ startSuperBlock += (int)heightInSuperBlocks;
+ }
+
+ if (startSuperBlock != superblockRowCount)
+ {
+ throw new ImageFormatException("Super block tiles height does not add up to total height.");
+ }
+
+ tileInfo.TileRowStartModeInfo[i] = frameHeader.ModeInfoRowCount;
+ tileInfo.TileRowCount = i;
+ tileInfo.TileRowCountLog2 = TileLog2(1, tileInfo.TileRowCount);
+ }
+
+ if (tileInfo.TileColumnCount > Av1Constants.MaxTileColumnCount || tileInfo.TileRowCount > Av1Constants.MaxTileRowCount)
+ {
+ throw new ImageFormatException("Tile width or height too big.");
+ }
+
+ if (tileInfo.TileColumnCountLog2 > 0 || tileInfo.TileRowCountLog2 > 0)
+ {
+ tileInfo.ContextUpdateTileId = reader.ReadLiteral(tileInfo.TileRowCountLog2 + tileInfo.TileColumnCountLog2);
+ tileInfo.TileSizeBytes = (int)reader.ReadLiteral(2) + 1;
+ }
+ else
+ {
+ tileInfo.ContextUpdateTileId = 0;
+ }
+
+ if (tileInfo.ContextUpdateTileId >= (tileInfo.TileColumnCount * tileInfo.TileRowCount))
+ {
+ throw new ImageFormatException("Context update Tile ID too large.");
+ }
+
+ return tileInfo;
+ }
+
+ ///
+ /// 5.9.2. Uncompressed header syntax.
+ ///
+ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader)
+ {
+ ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
+ ObuFrameHeader frameHeader = this.FrameHeader!;
+ int idLength = 0;
+ uint previousFrameId = 0;
+ bool frameSizeOverrideFlag = false;
+ if (sequenceHeader.IsFrameIdNumbersPresent)
+ {
+ idLength = sequenceHeader.FrameIdLength - 1 + sequenceHeader.DeltaFrameIdLength - 2 + 3;
+ DebugGuard.MustBeLessThanOrEqualTo(idLength, 16, nameof(idLength));
+ }
+
+ if (sequenceHeader.IsReducedStillPictureHeader)
+ {
+ frameHeader.ShowExistingFrame = false;
+ frameHeader.FrameType = ObuFrameType.KeyFrame;
+ frameHeader.ShowFrame = true;
+ frameHeader.ShowableFrame = false;
+ frameHeader.ErrorResilientMode = true;
+ }
+ else
+ {
+ frameHeader.ShowExistingFrame = reader.ReadBoolean();
+ if (frameHeader.ShowExistingFrame)
+ {
+ frameHeader.FrameToShowMapIdx = reader.ReadLiteral(3);
+
+ if (sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false)
+ {
+ // 5.9.31. Temporal point info syntax.
+ frameHeader.FramePresentationTime = reader.ReadLiteral((int)sequenceHeader!.DecoderModelInfo!.FramePresentationTimeLength);
+ }
+
+ if (sequenceHeader.IsFrameIdNumbersPresent)
+ {
+ frameHeader.DisplayFrameId = reader.ReadLiteral(idLength);
+ }
+
+ // TODO: This is incomplete here, not sure how we can display an already decoded frame here or if this is really relevent for still pictures.
+ throw new NotImplementedException("ShowExistingFrame is not yet implemented");
+ }
+
+ frameHeader.FrameType = (ObuFrameType)reader.ReadLiteral(2);
+ frameHeader.ShowFrame = reader.ReadBoolean();
+
+ if (frameHeader.ShowFrame && !sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false)
+ {
+ // 5.9.31. Temporal point info syntax.
+ frameHeader.FramePresentationTime = reader.ReadLiteral((int)sequenceHeader!.DecoderModelInfo!.FramePresentationTimeLength);
+ }
+
+ if (frameHeader.ShowFrame)
+ {
+ frameHeader.ShowableFrame = frameHeader.FrameType != ObuFrameType.KeyFrame;
+ }
+ else
+ {
+ frameHeader.ShowableFrame = reader.ReadBoolean();
+ }
+
+ if (frameHeader.FrameType == ObuFrameType.SwitchFrame || (frameHeader.FrameType == ObuFrameType.KeyFrame && frameHeader.ShowFrame))
+ {
+ frameHeader.ErrorResilientMode = true;
+ }
+ else
+ {
+ frameHeader.ErrorResilientMode = reader.ReadBoolean();
+ }
+ }
+
+ if (frameHeader.FrameType == ObuFrameType.KeyFrame && frameHeader.ShowFrame)
+ {
+ frameHeader.ReferenceValid = new bool[Av1Constants.ReferenceFrameCount];
+ frameHeader.ReferenceOrderHint = new bool[Av1Constants.ReferenceFrameCount];
+ Array.Fill(frameHeader.ReferenceValid, false);
+ Array.Fill(frameHeader.ReferenceOrderHint, false);
+ }
+
+ frameHeader.DisableCdfUpdate = reader.ReadBoolean();
+ frameHeader.AllowScreenContentTools = sequenceHeader.ForceScreenContentTools == 2;
+ if (frameHeader.AllowScreenContentTools)
+ {
+ frameHeader.AllowScreenContentTools = reader.ReadBoolean();
+ }
+
+ if (frameHeader.AllowScreenContentTools)
+ {
+ if (sequenceHeader.ForceIntegerMotionVector == 1)
+ {
+ frameHeader.ForceIntegerMotionVector = reader.ReadBoolean();
+ }
+ else
+ {
+ frameHeader.ForceIntegerMotionVector = sequenceHeader.ForceIntegerMotionVector != 0;
+ }
+ }
+ else
+ {
+ frameHeader.ForceIntegerMotionVector = false;
+ }
+
+ if (frameHeader.IsIntra)
+ {
+ frameHeader.ForceIntegerMotionVector = true;
+ }
+
+ bool havePreviousFrameId = !(frameHeader.FrameType == ObuFrameType.KeyFrame && frameHeader.ShowFrame);
+ if (havePreviousFrameId)
+ {
+ previousFrameId = frameHeader.CurrentFrameId;
+ }
+
+ if (sequenceHeader.IsFrameIdNumbersPresent)
+ {
+ frameHeader.CurrentFrameId = reader.ReadLiteral(idLength);
+ if (havePreviousFrameId)
+ {
+ uint diffFrameId = (frameHeader.CurrentFrameId > previousFrameId) ?
+ frameHeader.CurrentFrameId - previousFrameId :
+ (uint)((1 << idLength) + (int)frameHeader.CurrentFrameId - previousFrameId);
+ if (frameHeader.CurrentFrameId == previousFrameId || diffFrameId >= 1 << (idLength - 1))
+ {
+ throw new ImageFormatException("Current frame ID cannot be same as previous Frame ID");
+ }
+ }
+
+ int diffLength = sequenceHeader.DeltaFrameIdLength;
+ for (int i = 0; i < Av1Constants.ReferenceFrameCount; i++)
+ {
+ if (frameHeader.CurrentFrameId > (1U << diffLength))
+ {
+ if ((frameHeader.ReferenceFrameIndex[i] > frameHeader.CurrentFrameId) ||
+ frameHeader.ReferenceFrameIndex[i] > (frameHeader.CurrentFrameId - (1 - diffLength)))
+ {
+ frameHeader.ReferenceValid[i] = false;
+ }
+ }
+ else if (frameHeader.ReferenceFrameIndex[i] > frameHeader.CurrentFrameId &&
+ frameHeader.ReferenceFrameIndex[i] < ((1 << idLength) + (frameHeader.CurrentFrameId - (1 << diffLength))))
+ {
+ frameHeader.ReferenceValid[i] = false;
+ }
+ }
+ }
+ else
+ {
+ frameHeader.CurrentFrameId = 0;
+ }
+
+ if (frameHeader.FrameType == ObuFrameType.SwitchFrame)
+ {
+ frameSizeOverrideFlag = true;
+ }
+ else if (sequenceHeader.IsReducedStillPictureHeader)
+ {
+ frameSizeOverrideFlag = false;
+ }
+ else
+ {
+ frameSizeOverrideFlag = reader.ReadBoolean();
+ }
+
+ frameHeader.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits);
+
+ if (frameHeader.IsIntra || frameHeader.ErrorResilientMode)
+ {
+ frameHeader.PrimaryReferenceFrame = Av1Constants.PrimaryReferenceFrameNone;
+ }
+ else
+ {
+ frameHeader.PrimaryReferenceFrame = reader.ReadLiteral(Av1Constants.PimaryReferenceBits);
+ }
+
+ // Skipping, as no decoder info model present
+ frameHeader.AllowHighPrecisionMotionVector = false;
+ frameHeader.UseReferenceFrameMotionVectors = false;
+ frameHeader.AllowIntraBlockCopy = false;
+ if (frameHeader.FrameType == ObuFrameType.SwitchFrame || (frameHeader.FrameType == ObuFrameType.KeyFrame && frameHeader.ShowFrame))
+ {
+ frameHeader.RefreshFrameFlags = 0xFFU;
+ }
+ else
+ {
+ frameHeader.RefreshFrameFlags = reader.ReadLiteral(8);
+ }
+
+ if (frameHeader.FrameType == ObuFrameType.IntraOnlyFrame)
+ {
+ DebugGuard.IsTrue(frameHeader.RefreshFrameFlags != 0xFFU, nameof(frameHeader.RefreshFrameFlags));
+ }
+
+ if (!frameHeader.IsIntra || (frameHeader.RefreshFrameFlags != 0xFFU))
+ {
+ if (frameHeader.ErrorResilientMode && sequenceHeader.OrderHintInfo != null)
+ {
+ for (int i = 0; i < Av1Constants.ReferenceFrameCount; i++)
+ {
+ int referenceOrderHint = (int)reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits);
+ if (referenceOrderHint != (frameHeader.ReferenceOrderHint[i] ? 1U : 0U))
+ {
+ frameHeader.ReferenceValid[i] = false;
+ }
+ }
+ }
+ }
+
+ if (frameHeader.IsIntra)
+ {
+ this.ReadFrameSize(ref reader, frameSizeOverrideFlag);
+ this.ReadRenderSize(ref reader);
+ if (frameHeader.AllowScreenContentTools && frameHeader.FrameSize.RenderWidth != 0)
+ {
+ if (frameHeader.FrameSize.FrameWidth == frameHeader.FrameSize.SuperResolutionUpscaledWidth)
+ {
+ frameHeader.AllowIntraBlockCopy = reader.ReadBoolean();
+ }
+ }
+ }
+ else
+ {
+ // Single image is always Intra.
+ throw new InvalidImageContentException("AVIF image can only contain INTRA frames.");
+ }
+
+ // SetupFrameBufferReferences(sequenceHeader, frameHeader);
+ // CheckAddTemporalMotionVectorBuffer(sequenceHeader, frameHeader);
+
+ // SetupFrameSignBias(sequenceHeader, frameHeader);
+ if (sequenceHeader.IsReducedStillPictureHeader || frameHeader.DisableCdfUpdate)
+ {
+ frameHeader.DisableFrameEndUpdateCdf = true;
+ }
+ else
+ {
+ frameHeader.DisableFrameEndUpdateCdf = reader.ReadBoolean();
+ }
+
+ if (frameHeader.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone)
+ {
+ // InitConCoefficientCdfs();
+ // SetupPastIndependence(frameHeader);
+ }
+ else
+ {
+ // LoadCdfs(frameHeader.PrimaryReferenceFrame);
+ // LoadPrevious();
+ throw new NotImplementedException();
+ }
+
+ if (frameHeader.UseReferenceFrameMotionVectors)
+ {
+ // MotionFieldEstimations();
+ throw new NotImplementedException();
+ }
+
+ // GenerateNextReferenceFrameMap(sequenceHeader, frameHeader);
+ frameHeader.TilesInfo = ReadTileInfo(ref reader, sequenceHeader, frameHeader);
+ ReadQuantizationParameters(ref reader, sequenceHeader, frameHeader);
+ ReadSegmentationParameters(ref reader, frameHeader);
+ ReadFrameDeltaQParameters(ref reader, frameHeader);
+ ReadFrameDeltaLoopFilterParameters(ref reader, frameHeader);
+
+ // SetupSegmentationDequantization();
+ if (frameHeader.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone)
+ {
+ // ResetParseContext(mainParseContext, frameHeader.QuantizationParameters.BaseQIndex);
+ }
+ else
+ {
+ // LoadPreviousSegmentIds();
+ throw new NotImplementedException();
+ }
+
+ int tilesCount = frameHeader.TilesInfo.TileColumnCount * frameHeader.TilesInfo.TileRowCount;
+ frameHeader.CodedLossless = true;
+ frameHeader.SegmentationParameters.QMLevel[0] = new int[Av1Constants.MaxSegmentCount];
+ frameHeader.SegmentationParameters.QMLevel[1] = new int[Av1Constants.MaxSegmentCount];
+ frameHeader.SegmentationParameters.QMLevel[2] = new int[Av1Constants.MaxSegmentCount];
+ for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++)
+ {
+ int qIndex = Av1QuantizationLookup.GetQIndex(frameHeader.SegmentationParameters, segmentId, frameHeader.QuantizationParameters.BaseQIndex);
+ frameHeader.QuantizationParameters.QIndex[segmentId] = qIndex;
+ frameHeader.LosslessArray[segmentId] = qIndex == 0 &&
+ frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.Y] == 0 &&
+ frameHeader.QuantizationParameters.DeltaQAc[(int)Av1Plane.U] == 0 &&
+ frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.U] == 0 &&
+ frameHeader.QuantizationParameters.DeltaQAc[(int)Av1Plane.V] == 0 &&
+ frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.V] == 0;
+ if (!frameHeader.LosslessArray[segmentId])
+ {
+ frameHeader.CodedLossless = false;
+ }
+
+ if (frameHeader.QuantizationParameters.IsUsingQMatrix)
+ {
+ if (frameHeader.LosslessArray[segmentId])
+ {
+ frameHeader.SegmentationParameters.QMLevel[0][segmentId] = 15;
+ frameHeader.SegmentationParameters.QMLevel[1][segmentId] = 15;
+ frameHeader.SegmentationParameters.QMLevel[2][segmentId] = 15;
+ }
+ else
+ {
+ frameHeader.SegmentationParameters.QMLevel[0][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.Y];
+ frameHeader.SegmentationParameters.QMLevel[1][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.U];
+ frameHeader.SegmentationParameters.QMLevel[2][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.V];
+ }
+ }
+ }
+
+ if (frameHeader.CodedLossless)
+ {
+ DebugGuard.IsFalse(frameHeader.DeltaQParameters.IsPresent, nameof(frameHeader.DeltaQParameters.IsPresent), "No Delta Q parameters are allowed for lossless frame.");
+ }
+
+ frameHeader.AllLossless = frameHeader.CodedLossless && frameHeader.FrameSize.FrameWidth == frameHeader.FrameSize.SuperResolutionUpscaledWidth;
+ this.ReadLoopFilterParameters(ref reader, sequenceHeader);
+ ReadCdefParameters(ref reader, sequenceHeader, frameHeader);
+ ReadLoopRestorationParameters(ref reader, sequenceHeader, frameHeader);
+ ReadTransformMode(ref reader, frameHeader);
+
+ frameHeader.ReferenceMode = ReadFrameReferenceMode(ref reader, frameHeader);
+ ReadSkipModeParameters(ref reader, sequenceHeader, frameHeader);
+ if (frameHeader.IsIntra || frameHeader.ErrorResilientMode || !sequenceHeader.EnableWarpedMotion)
+ {
+ frameHeader.AllowWarpedMotion = false;
+ }
+ else
+ {
+ frameHeader.AllowWarpedMotion = reader.ReadBoolean();
+ }
+
+ frameHeader.UseReducedTransformSet = reader.ReadBoolean();
+ ReadGlobalMotionParameters(ref reader, sequenceHeader, frameHeader);
+ frameHeader.FilmGrainParameters = ReadFilmGrainFilterParameters(ref reader, sequenceHeader, frameHeader);
+ }
+
+ private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature)
+ => segmentationParameters.Enabled && segmentationParameters.IsFeatureActive(segmentId, feature);
+
+ ///
+ /// 5.9.1. General frame header OBU syntax.
+ ///
+ internal void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit)
+ {
+ int startBitPosition = reader.BitPosition;
+ this.ReadUncompressedFrameHeader(ref reader);
+ if (trailingBit)
+ {
+ ReadTrailingBits(ref reader);
+ }
+
+ AlignToByteBoundary(ref reader);
+
+ int endPosition = reader.BitPosition;
+ int headerBytes = (endPosition - startBitPosition) / 8;
+ header.PayloadSize -= headerBytes;
+ }
+
+ ///
+ /// 5.11.1. General tile group OBU syntax.
+ ///
+ private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileReader decoder, ObuHeader header, out bool isLastTileGroup)
+ {
+ ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
+ ObuFrameHeader frameHeader = this.FrameHeader!;
+ ObuTileGroupHeader tileInfo = this.FrameHeader!.TilesInfo;
+ int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
+ int startBitPosition = reader.BitPosition;
+ bool tileStartAndEndPresentFlag = false;
+ if (tileCount > 1)
+ {
+ tileStartAndEndPresentFlag = reader.ReadBoolean();
+ }
+
+ if (header.Type == ObuType.FrameHeader)
+ {
+ DebugGuard.IsFalse(tileStartAndEndPresentFlag, nameof(tileStartAndEndPresentFlag), "Frame header should not set 'tileStartAndEndPresentFlag'.");
+ }
+
+ int tileGroupStart = 0;
+ int tileGroupEnd = tileCount - 1;
+ if (tileCount != 1 && tileStartAndEndPresentFlag)
+ {
+ int tileBits = tileInfo.TileColumnCountLog2 + tileInfo.TileRowCountLog2;
+ tileGroupStart = (int)reader.ReadLiteral(tileBits);
+ tileGroupEnd = (int)reader.ReadLiteral(tileBits);
+ }
+
+ isLastTileGroup = (tileGroupEnd + 1) == tileCount;
+ AlignToByteBoundary(ref reader);
+ int endBitPosition = reader.BitPosition;
+ int headerBytes = (endBitPosition - startBitPosition) / 8;
+ header.PayloadSize -= headerBytes;
+
+ bool noIbc = !frameHeader.AllowIntraBlockCopy;
+ bool doLoopFilter = noIbc && (frameHeader.LoopFilterParameters.FilterLevel[0] != 0 || frameHeader.LoopFilterParameters.FilterLevel[1] != 0);
+ bool doCdef = noIbc && (!frameHeader.CodedLossless &&
+ (frameHeader.CdefParameters.BitCount != 0 ||
+ frameHeader.CdefParameters.YStrength[0] != 0 ||
+ frameHeader.CdefParameters.UvStrength[0] != 0));
+ bool doLoopRestoration = noIbc &&
+ (frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.Y].Type != ObuRestorationType.None ||
+ frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.U].Type != ObuRestorationType.None ||
+ frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.V].Type != ObuRestorationType.None);
+
+ for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++)
+ {
+ bool isLastTile = tileNum == tileGroupEnd;
+ int tileDataSize = header.PayloadSize;
+ if (!isLastTile)
+ {
+ tileDataSize = (int)reader.ReadLittleEndian(tileInfo.TileSizeBytes) + 1;
+ header.PayloadSize -= tileDataSize + tileInfo.TileSizeBytes;
+ }
+
+ Span tileData = reader.GetSymbolReader(tileDataSize);
+ decoder.ReadTile(tileData, tileNum);
+ }
+
+ if (tileGroupEnd != tileCount - 1)
+ {
+ return;
+ }
+
+ // TODO: Share doCdef and doLoopRestoration
+ }
+
+ ///
+ /// 5.9.13. Delta quantizer syntax.
+ ///
+ private static int ReadDeltaQ(ref Av1BitStreamReader reader)
+ {
+ int deltaQ = 0;
+ if (reader.ReadBoolean())
+ {
+ deltaQ = reader.ReadSignedFromUnsigned(7);
+ }
+
+ return deltaQ;
+ }
+
+ ///
+ /// 5.9.17. Quantizer index delta parameters syntax.
+ ///
+ private static void ReadFrameDeltaQParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
+ {
+ frameHeader.DeltaQParameters.Resolution = 0;
+ frameHeader.DeltaQParameters.IsPresent = false;
+ if (frameHeader.QuantizationParameters.BaseQIndex > 0)
+ {
+ frameHeader.DeltaQParameters.IsPresent = reader.ReadBoolean();
+ }
+
+ if (frameHeader.DeltaQParameters.IsPresent)
+ {
+ frameHeader.DeltaQParameters.Resolution = (int)reader.ReadLiteral(2);
+ }
+ }
+
+ ///
+ /// 5.9.18. Loop filter delta parameters syntax.
+ ///
+ private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
+ {
+ frameHeader.DeltaLoopFilterParameters.IsPresent = false;
+ frameHeader.DeltaLoopFilterParameters.Resolution = 0;
+ frameHeader.DeltaLoopFilterParameters.IsMulti = false;
+ if (frameHeader.DeltaQParameters.IsPresent)
+ {
+ if (!frameHeader.AllowIntraBlockCopy)
+ {
+ frameHeader.DeltaLoopFilterParameters.IsPresent = reader.ReadBoolean();
+ }
+
+ if (frameHeader.DeltaLoopFilterParameters.IsPresent)
+ {
+ frameHeader.DeltaLoopFilterParameters.Resolution = (int)reader.ReadLiteral(2);
+ frameHeader.DeltaLoopFilterParameters.IsMulti = reader.ReadBoolean();
+ }
+ }
+ }
+
+ ///
+ /// 5.9.12. Quantization params syntax.
+ ///
+ private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ ObuQuantizationParameters quantParams = frameHeader.QuantizationParameters;
+ ObuColorConfig colorInfo = sequenceHeader.ColorConfig;
+ quantParams.BaseQIndex = (int)reader.ReadLiteral(8);
+ quantParams.DeltaQDc[(int)Av1Plane.Y] = ReadDeltaQ(ref reader);
+ quantParams.DeltaQAc[(int)Av1Plane.Y] = 0;
+ if (colorInfo.PlaneCount > 1)
+ {
+ quantParams.HasSeparateUvDelta = false;
+ if (colorInfo.HasSeparateUvDelta)
+ {
+ quantParams.HasSeparateUvDelta = reader.ReadBoolean();
+ }
+
+ quantParams.DeltaQDc[(int)Av1Plane.U] = ReadDeltaQ(ref reader);
+ quantParams.DeltaQAc[(int)Av1Plane.U] = ReadDeltaQ(ref reader);
+ if (quantParams.HasSeparateUvDelta)
+ {
+ quantParams.DeltaQDc[(int)Av1Plane.V] = ReadDeltaQ(ref reader);
+ quantParams.DeltaQAc[(int)Av1Plane.V] = ReadDeltaQ(ref reader);
+ }
+ else
+ {
+ quantParams.DeltaQDc[(int)Av1Plane.V] = quantParams.DeltaQDc[(int)Av1Plane.U];
+ quantParams.DeltaQAc[(int)Av1Plane.V] = quantParams.DeltaQAc[(int)Av1Plane.U];
+ }
+ }
+ else
+ {
+ quantParams.DeltaQDc[(int)Av1Plane.U] = 0;
+ quantParams.DeltaQAc[(int)Av1Plane.U] = 0;
+ quantParams.DeltaQDc[(int)Av1Plane.V] = 0;
+ quantParams.DeltaQAc[(int)Av1Plane.V] = 0;
+ }
+
+ quantParams.IsUsingQMatrix = reader.ReadBoolean();
+ if (quantParams.IsUsingQMatrix)
+ {
+ quantParams.QMatrix[(int)Av1Plane.Y] = (int)reader.ReadLiteral(4);
+ quantParams.QMatrix[(int)Av1Plane.U] = (int)reader.ReadLiteral(4);
+ if (!colorInfo.HasSeparateUvDelta)
+ {
+ quantParams.QMatrix[(int)Av1Plane.V] = quantParams.QMatrix[(int)Av1Plane.U];
+ }
+ else
+ {
+ quantParams.QMatrix[(int)Av1Plane.V] = (int)reader.ReadLiteral(4);
+ }
+ }
+ else
+ {
+ quantParams.QMatrix[(int)Av1Plane.Y] = 0;
+ quantParams.QMatrix[(int)Av1Plane.U] = 0;
+ quantParams.QMatrix[(int)Av1Plane.V] = 0;
+ }
+ }
+
+ ///
+ /// 5.9.14. Segmentation params syntax.
+ ///
+ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
+ {
+ frameHeader.SegmentationParameters.Enabled = reader.ReadBoolean();
+
+ if (frameHeader.SegmentationParameters.Enabled)
+ {
+ if (frameHeader.PrimaryReferenceFrame == PrimaryRefNone)
+ {
+ frameHeader.SegmentationParameters.SegmentationUpdateMap = 1;
+ frameHeader.SegmentationParameters.SegmentationTemporalUpdate = 0;
+ frameHeader.SegmentationParameters.SegmentationUpdateData = 1;
+ }
+ else
+ {
+ frameHeader.SegmentationParameters.SegmentationUpdateMap = reader.ReadBoolean() ? 1 : 0;
+ if (frameHeader.SegmentationParameters.SegmentationUpdateMap == 1)
+ {
+ frameHeader.SegmentationParameters.SegmentationTemporalUpdate = reader.ReadBoolean() ? 1 : 0;
+ }
+
+ frameHeader.SegmentationParameters.SegmentationUpdateData = reader.ReadBoolean() ? 1 : 0;
+ }
+
+ if (frameHeader.SegmentationParameters.SegmentationUpdateData == 1)
+ {
+ for (int i = 0; i < MaxSegments; i++)
+ {
+ for (int j = 0; j < SegLvlMax; j++)
+ {
+ int featureValue = 0;
+ bool featureEnabled = reader.ReadBoolean();
+ frameHeader.SegmentationParameters.FeatureEnabled[i, j] = featureEnabled;
+ int clippedValue = 0;
+ if (featureEnabled)
+ {
+ int bitsToRead = SegmentationFeatureBits[j];
+ int limit = SegmentationFeatureMax[j];
+ if (SegmentationFeatureSigned[j] == 1)
+ {
+ featureValue = reader.ReadSignedFromUnsigned(1 + bitsToRead);
+ clippedValue = Av1Math.Clip3(-limit, limit, featureValue);
+ }
+ else
+ {
+ featureValue = (int)reader.ReadLiteral(bitsToRead);
+ }
+ }
+
+ frameHeader.SegmentationParameters.FeatureData[i, j] = clippedValue;
+ }
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Av1Constants.MaxSegmentCount; i++)
+ {
+ for (int j = 0; j < Av1Constants.SegmentationLevelMax; j++)
+ {
+ frameHeader.SegmentationParameters.FeatureEnabled[i, j] = false;
+ frameHeader.SegmentationParameters.FeatureData[i, j] = 0;
+ }
+ }
+ }
+
+ frameHeader.SegmentationParameters.SegmentIdPrecedesSkip = false;
+ frameHeader.SegmentationParameters.LastActiveSegmentId = 0;
+ for (int i = 0; i < Av1Constants.MaxSegmentCount; i++)
+ {
+ for (int j = 0; j < Av1Constants.SegmentationLevelMax; j++)
+ {
+ if (frameHeader.SegmentationParameters.FeatureEnabled[i, j])
+ {
+ frameHeader.SegmentationParameters.LastActiveSegmentId = i;
+ if (j >= SegLvlRefFrame)
+ {
+ frameHeader.SegmentationParameters.SegmentIdPrecedesSkip = true;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// 5.9.11. Loop filter params syntax
+ ///
+ private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
+ {
+ ObuFrameHeader frameHeader = this.FrameHeader!;
+ if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy)
+ {
+ return;
+ }
+
+ frameHeader.LoopFilterParameters.FilterLevel[0] = (int)reader.ReadLiteral(6);
+ frameHeader.LoopFilterParameters.FilterLevel[1] = (int)reader.ReadLiteral(6);
+
+ if (sequenceHeader.ColorConfig.PlaneCount > 1)
+ {
+ if (frameHeader.LoopFilterParameters.FilterLevel[0] > 0 || frameHeader.LoopFilterParameters.FilterLevel[1] > 0)
+ {
+ frameHeader.LoopFilterParameters.FilterLevelU = (int)reader.ReadLiteral(6);
+ frameHeader.LoopFilterParameters.FilterLevelV = (int)reader.ReadLiteral(6);
+ }
+ }
+
+ frameHeader.LoopFilterParameters.SharpnessLevel = (int)reader.ReadLiteral(3);
+ frameHeader.LoopFilterParameters.ReferenceDeltaModeEnabled = reader.ReadBoolean();
+ if (frameHeader.LoopFilterParameters.ReferenceDeltaModeEnabled)
+ {
+ frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate = reader.ReadBoolean();
+ if (frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate)
+ {
+ for (int i = 0; i < Av1Constants.TotalReferencesPerFrame; i++)
+ {
+ if (reader.ReadBoolean())
+ {
+ frameHeader.LoopFilterParameters.ReferenceDeltas[i] = reader.ReadSignedFromUnsigned(7);
+ }
+ }
+
+ for (int i = 0; i < 2; i++)
+ {
+ if (reader.ReadBoolean())
+ {
+ frameHeader.LoopFilterParameters.ModeDeltas[i] = reader.ReadSignedFromUnsigned(7);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// 5.9.21. TX mode syntax.
+ ///
+ private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
+ {
+ if (frameHeader.CodedLossless)
+ {
+ frameHeader.TransformMode = Av1TransformMode.Only4x4;
+ }
+ else
+ {
+ if (reader.ReadBoolean())
+ {
+ frameHeader.TransformMode = Av1TransformMode.Select;
+ }
+ else
+ {
+ frameHeader.TransformMode = Av1TransformMode.Largest;
+ }
+ }
+ }
+
+ ///
+ /// See section 5.9.20. Loop restoration params syntax.
+ ///
+ private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration)
+ {
+ return;
+ }
+
+ frameHeader.LoopRestorationParameters.UsesLoopRestoration = false;
+ frameHeader.LoopRestorationParameters.UsesChromaLoopRestoration = false;
+ int planesCount = sequenceHeader.ColorConfig.PlaneCount;
+ for (int i = 0; i < planesCount; i++)
+ {
+ frameHeader.LoopRestorationParameters.Items[i].Type = (ObuRestorationType)reader.ReadLiteral(2);
+
+ if (frameHeader.LoopRestorationParameters.Items[i].Type != ObuRestorationType.None)
+ {
+ frameHeader.LoopRestorationParameters.UsesLoopRestoration = true;
+ if (i > 0)
+ {
+ frameHeader.LoopRestorationParameters.UsesChromaLoopRestoration = true;
+ }
+ }
+ }
+
+ if (frameHeader.LoopRestorationParameters.UsesLoopRestoration)
+ {
+ frameHeader.LoopRestorationParameters.UnitShift = (int)reader.ReadLiteral(1);
+ if (sequenceHeader.Use128x128Superblock)
+ {
+ frameHeader.LoopRestorationParameters.UnitShift++;
+ }
+ else
+ {
+ if (reader.ReadBoolean())
+ {
+ frameHeader.LoopRestorationParameters.UnitShift += (int)reader.ReadLiteral(1);
+ }
+ }
+
+ frameHeader.LoopRestorationParameters.Items[0].Size = Av1Constants.RestorationMaxTileSize >> (2 - frameHeader.LoopRestorationParameters.UnitShift);
+ frameHeader.LoopRestorationParameters.UVShift = 0;
+ if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && frameHeader.LoopRestorationParameters.UsesChromaLoopRestoration)
+ {
+ frameHeader.LoopRestorationParameters.UVShift = (int)reader.ReadLiteral(1);
+ }
+
+ frameHeader.LoopRestorationParameters.Items[1].Size = frameHeader.LoopRestorationParameters.Items[0].Size >> frameHeader.LoopRestorationParameters.UVShift;
+ frameHeader.LoopRestorationParameters.Items[2].Size = frameHeader.LoopRestorationParameters.Items[0].Size >> frameHeader.LoopRestorationParameters.UVShift;
+ }
+ }
+
+ ///
+ /// See section 5.9.19. CDEF params syntax.
+ ///
+ private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ ObuConstraintDirectionalEnhancementFilterParameters cdefInfo = frameHeader.CdefParameters;
+ bool multiPlane = sequenceHeader.ColorConfig.PlaneCount > 1;
+ if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || sequenceHeader.CdefLevel == 0)
+ {
+ cdefInfo.BitCount = 0;
+ cdefInfo.YStrength[0] = 0;
+ cdefInfo.YStrength[4] = 0;
+ cdefInfo.UvStrength[0] = 0;
+ cdefInfo.UvStrength[4] = 0;
+ cdefInfo.Damping = 0;
+ return;
+ }
+
+ cdefInfo.Damping = (int)reader.ReadLiteral(2) + 3;
+ cdefInfo.BitCount = (int)reader.ReadLiteral(2);
+ for (int i = 0; i < (1 << frameHeader.CdefParameters.BitCount); i++)
+ {
+ cdefInfo.YStrength[i] = (int)reader.ReadLiteral(6);
+
+ if (multiPlane)
+ {
+ cdefInfo.UvStrength[i] = (int)reader.ReadLiteral(6);
+ }
+ }
+ }
+
+ ///
+ /// 5.9.24. Global motion params syntax.
+ ///
+ private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ _ = reader;
+ _ = sequenceHeader;
+
+ if (frameHeader.IsIntra)
+ {
+ return;
+ }
+
+ // Not applicable for INTRA frames.
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// 5.9.23. Frame reference mode syntax
+ ///
+ private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
+ {
+ if (frameHeader.IsIntra)
+ {
+ return ObuReferenceMode.SingleReference;
+ }
+
+ return (ObuReferenceMode)reader.ReadLiteral(1);
+ }
+
+ ///
+ /// 5.11.10. Skip mode syntax.
+ ///
+ private static void ReadSkipModeParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ if (frameHeader.IsIntra || frameHeader.ReferenceMode == ObuReferenceMode.ReferenceModeSelect || !sequenceHeader.OrderHintInfo.EnableOrderHint)
+ {
+ frameHeader.SkipModeParameters.SkipModeAllowed = false;
+ }
+ else
+ {
+ // Not applicable for INTRA frames.
+ }
+
+ if (frameHeader.SkipModeParameters.SkipModeAllowed)
+ {
+ frameHeader.SkipModeParameters.SkipModeFlag = reader.ReadBoolean();
+ }
+ else
+ {
+ frameHeader.SkipModeParameters.SkipModeFlag = false;
+ }
+ }
+
+ ///
+ /// 5.9.30. Film grain params syntax.
+ ///
+ private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ ObuFilmGrainParameters grainParams = new();
+ if (!sequenceHeader.AreFilmGrainingParametersPresent || (!frameHeader.ShowFrame && !frameHeader.ShowableFrame))
+ {
+ return grainParams;
+ }
+
+ grainParams.ApplyGrain = reader.ReadBoolean();
+ if (!grainParams.ApplyGrain)
+ {
+ return grainParams;
+ }
+
+ grainParams.GrainSeed = reader.ReadLiteral(16);
+
+ if (frameHeader.FrameType == ObuFrameType.InterFrame)
+ {
+ grainParams.UpdateGrain = reader.ReadBoolean();
+ }
+ else
+ {
+ grainParams.UpdateGrain = false;
+ }
+
+ if (!grainParams.UpdateGrain)
+ {
+ grainParams.FilmGrainParamsRefidx = reader.ReadLiteral(3);
+ uint tempGrainSeed = grainParams.GrainSeed;
+
+ // TODO: implement load_grain_params
+ // load_grain_params(film_grain_params_ref_idx)
+ grainParams.GrainSeed = tempGrainSeed;
+ return grainParams;
+ }
+
+ grainParams.NumYPoints = reader.ReadLiteral(4);
+ grainParams.PointYValue = new uint[grainParams.NumYPoints];
+ grainParams.PointYScaling = new uint[grainParams.NumYPoints];
+ for (int i = 0; i < grainParams.NumYPoints; i++)
+ {
+ grainParams.PointYValue[i] = reader.ReadLiteral(8);
+ grainParams.PointYScaling[i] = reader.ReadLiteral(8);
+ }
+
+ if (sequenceHeader.ColorConfig.IsMonochrome)
+ {
+ grainParams.ChromaScalingFromLuma = false;
+ }
+ else
+ {
+ grainParams.ChromaScalingFromLuma = reader.ReadBoolean();
+ }
+
+ if (sequenceHeader.ColorConfig.IsMonochrome ||
+ grainParams.ChromaScalingFromLuma ||
+ (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && grainParams.NumYPoints == 0))
+ {
+ grainParams.NumCbPoints = 0;
+ grainParams.NumCrPoints = 0;
+ }
+ else
+ {
+ grainParams.NumCbPoints = reader.ReadLiteral(4);
+ grainParams.PointCbValue = new uint[grainParams.NumCbPoints];
+ grainParams.PointCbScaling = new uint[grainParams.NumCbPoints];
+ for (int i = 0; i < grainParams.NumCbPoints; i++)
+ {
+ grainParams.PointCbValue[i] = reader.ReadLiteral(8);
+ grainParams.PointCbScaling[i] = reader.ReadLiteral(8);
+ }
+
+ grainParams.NumCrPoints = reader.ReadLiteral(4);
+ grainParams.PointCrValue = new uint[grainParams.NumCrPoints];
+ grainParams.PointCrScaling = new uint[grainParams.NumCrPoints];
+ for (int i = 0; i < grainParams.NumCbPoints; i++)
+ {
+ grainParams.PointCrValue[i] = reader.ReadLiteral(8);
+ grainParams.PointCrScaling[i] = reader.ReadLiteral(8);
+ }
+ }
+
+ grainParams.GrainScalingMinus8 = reader.ReadLiteral(2);
+ grainParams.ArCoeffLag = reader.ReadLiteral(2);
+ uint numPosLuma = 2 * grainParams.ArCoeffLag * (grainParams.ArCoeffLag + 1);
+
+ uint numPosChroma = 0;
+ if (grainParams.NumYPoints != 0)
+ {
+ numPosChroma = numPosLuma + 1;
+ grainParams.ArCoeffsYPlus128 = new uint[numPosLuma];
+ for (int i = 0; i < numPosLuma; i++)
+ {
+ grainParams.ArCoeffsYPlus128[i] = reader.ReadLiteral(8);
+ }
+ }
+ else
+ {
+ numPosChroma = numPosLuma;
+ }
+
+ if (grainParams.ChromaScalingFromLuma || grainParams.NumCbPoints != 0)
+ {
+ grainParams.ArCoeffsCbPlus128 = new uint[numPosChroma];
+ for (int i = 0; i < numPosChroma; i++)
+ {
+ grainParams.ArCoeffsCbPlus128[i] = reader.ReadLiteral(8);
+ }
+ }
+
+ if (grainParams.ChromaScalingFromLuma || grainParams.NumCrPoints != 0)
+ {
+ grainParams.ArCoeffsCrPlus128 = new uint[numPosChroma];
+ for (int i = 0; i < numPosChroma; i++)
+ {
+ grainParams.ArCoeffsCrPlus128[i] = reader.ReadLiteral(8);
+ }
+ }
+
+ grainParams.ArCoeffShiftMinus6 = reader.ReadLiteral(2);
+ grainParams.GrainScaleShift = reader.ReadLiteral(2);
+ if (grainParams.NumCbPoints != 0)
+ {
+ grainParams.CbMult = reader.ReadLiteral(8);
+ grainParams.CbLumaMult = reader.ReadLiteral(8);
+ grainParams.CbOffset = reader.ReadLiteral(9);
+ }
+
+ if (grainParams.NumCrPoints != 0)
+ {
+ grainParams.CrMult = reader.ReadLiteral(8);
+ grainParams.CrLumaMult = reader.ReadLiteral(8);
+ grainParams.CrOffset = reader.ReadLiteral(9);
+ }
+
+ grainParams.OverlapFlag = reader.ReadBoolean();
+ grainParams.ClipToRestrictedRange = reader.ReadBoolean();
+
+ return grainParams;
+ }
+
+ private static bool IsValidSequenceLevel(int sequenceLevelIndex)
+ => sequenceLevelIndex is < 24 or 31;
+
+ ///
+ /// Returns the smallest value for k such that blockSize << k is greater than or equal to target.
+ ///
+ public static int TileLog2(int blockSize, int target)
+ {
+ int k;
+ for (k = 0; (blockSize << k) < target; k++)
+ {
+ }
+
+ return k;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs
new file mode 100644
index 0000000000..a76bc7601e
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs
@@ -0,0 +1,10 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal enum ObuReferenceMode
+{
+ ReferenceModeSelect,
+ SingleReference,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs
new file mode 100644
index 0000000000..bf90f5021d
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal enum ObuRestorationType : uint
+{
+ None = 0,
+ Switchable = 1,
+ Weiner = 2,
+ SgrProj = 3,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs
new file mode 100644
index 0000000000..93af8e0a1c
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal enum ObuSegmentationLevelFeature
+{
+ AlternativeQuantizer,
+ AlternativeLoopFilterYVertical,
+ AlternativeLoopFilterYHorizontal,
+ AlternativeLoopFilterU,
+ AlternativeLoopFilterV,
+ ReferenceFrame,
+ Skip,
+ GlobalMotionVector,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs
new file mode 100644
index 0000000000..a31235322c
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuSegmentationParameters
+{
+ public int[][] QMLevel { get; internal set; } = new int[3][];
+
+ public bool[,] FeatureEnabled { get; internal set; } = new bool[Av1Constants.MaxSegmentCount, Av1Constants.SegmentationLevelMax];
+
+ public bool Enabled { get; internal set; }
+
+ public int[,] FeatureData { get; internal set; } = new int[Av1Constants.MaxSegmentCount, Av1Constants.SegmentationLevelMax];
+
+ public bool SegmentIdPrecedesSkip { get; internal set; }
+
+ public int LastActiveSegmentId { get; internal set; }
+
+ ///
+ /// Gets or sets the SegmentationUpdateMap. A value of 1 indicates that the segmentation map are updated during the decoding of this
+ /// frame. SegmentationUpdateMap equal to 0 means that the segmentation map from the previous frame is used.
+ ///
+ public int SegmentationUpdateMap { get; internal set; }
+
+ ///
+ /// Gets or sets the SegmentationTemporalUpdate. A value of 1 indicates that the updates to the segmentation map are coded relative to the
+ /// existing segmentation map. SegmentationTemporalUpdate equal to 0 indicates that the new segmentation map is coded
+ /// without reference to the existing segmentation map.
+ ///
+ public int SegmentationTemporalUpdate { get; internal set; }
+
+ ///
+ /// Gets or sets SegmentationUpdateData. A value of 1 indicates that new parameters are about to be specified for each segment.
+ /// SegmentationUpdateData equal to 0 indicates that the segmentation parameters should keep their existing values.
+ ///
+ public int SegmentationUpdateData { get; internal set; }
+
+ internal bool IsFeatureActive(int segmentId, ObuSegmentationLevelFeature feature)
+ => this.FeatureEnabled[segmentId, (int)feature];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs
new file mode 100644
index 0000000000..ac87eefe74
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuSequenceHeader
+{
+ private bool use128x128Superblock;
+
+ public bool EnableFilterIntra { get; set; }
+
+ public bool EnableCdef { get; set; }
+
+ public bool IsStillPicture { get; set; }
+
+ public bool IsReducedStillPictureHeader { get; set; }
+
+ public ObuSequenceProfile SequenceProfile { get; set; }
+
+ public ObuOperatingPoint[] OperatingPoint { get; set; } = new ObuOperatingPoint[1];
+
+ public ObuDecoderModelInfo? DecoderModelInfo { get; set; }
+
+ public bool InitialDisplayDelayPresentFlag { get; set; }
+
+ public bool DecoderModelInfoPresentFlag { get; set; }
+
+ public bool TimingInfoPresentFlag { get; set; }
+
+ public ObuTimingInfo? TimingInfo { get; set; }
+
+ public bool IsFrameIdNumbersPresent { get; set; }
+
+ public int FrameWidthBits { get; set; }
+
+ public int FrameHeightBits { get; set; }
+
+ public int MaxFrameWidth { get; set; }
+
+ public int MaxFrameHeight { get; set; }
+
+ public bool Use128x128Superblock
+ {
+ get => this.use128x128Superblock;
+ set
+ {
+ this.use128x128Superblock = value;
+ this.SuperblockSize = value ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
+ this.SuperblockSizeLog2 = value ? 7 : 6;
+ this.SuperblockModeInfoSize = 1 << (this.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2);
+ }
+ }
+
+ public Av1BlockSize SuperblockSize { get; private set; }
+
+ public int SuperblockModeInfoSize { get; private set; }
+
+ public int SuperblockSizeLog2 { get; private set; }
+
+ public int FilterIntraLevel { get; set; }
+
+ public bool EnableIntraEdgeFilter { get; set; }
+
+ public ObuOrderHintInfo OrderHintInfo { get; set; } = new ObuOrderHintInfo();
+
+ public bool EnableOrderHint { get; set; }
+
+ public bool EnableInterIntraCompound { get; set; }
+
+ public bool EnableMaskedCompound { get; set; }
+
+ public bool EnableWarpedMotion { get; set; }
+
+ public bool EnableDualFilter { get; set; }
+
+ public int ForceIntegerMotionVector { get; set; }
+
+ public int ForceScreenContentTools { get; set; }
+
+ public bool EnableSuperResolution { get; set; }
+
+ public int CdefLevel { get; set; }
+
+ public bool EnableRestoration { get; set; }
+
+ public ObuColorConfig ColorConfig { get; set; } = new ObuColorConfig();
+
+ public bool AreFilmGrainingParametersPresent { get; set; }
+
+ public int FrameIdLength { get; set; }
+
+ public int DeltaFrameIdLength { get; set; }
+
+ public uint AdditionalFrameIdLength { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs
new file mode 100644
index 0000000000..f1ac85edb3
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal enum ObuSequenceProfile : uint
+{
+ Main = 0,
+ High = 1,
+ Professional = 2,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSkipModeParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSkipModeParameters.cs
new file mode 100644
index 0000000000..29a87a5f23
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSkipModeParameters.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuSkipModeParameters
+{
+ public bool SkipModeAllowed { get; set; }
+
+ public bool SkipModeFlag { get; internal set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs
new file mode 100644
index 0000000000..a5f1867b66
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuTileGroupHeader
+{
+ internal int MaxTileWidthSuperblock { get; set; }
+
+ internal int MaxTileHeightSuperblock { get; set; }
+
+ internal int MinLog2TileColumnCount { get; set; }
+
+ internal int MaxLog2TileColumnCount { get; set; }
+
+ internal int MaxLog2TileRowCount { get; set; }
+
+ internal int MinLog2TileCount { get; set; }
+
+ public bool HasUniformTileSpacing { get; set; }
+
+ internal int TileColumnCountLog2 { get; set; }
+
+ internal int TileColumnCount { get; set; }
+
+ internal int[] TileColumnStartModeInfo { get; set; } = new int[Av1Constants.MaxTileRowCount + 1];
+
+ internal int MinLog2TileRowCount { get; set; }
+
+ internal int TileRowCountLog2 { get; set; }
+
+ internal int[] TileRowStartModeInfo { get; set; } = new int[Av1Constants.MaxTileColumnCount + 1];
+
+ internal int TileRowCount { get; set; }
+
+ internal uint ContextUpdateTileId { get; set; }
+
+ internal int TileSizeBytes { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTimingInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTimingInfo.cs
new file mode 100644
index 0000000000..d366157e78
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTimingInfo.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuTimingInfo
+{
+ ///
+ /// Gets or sets NumUnitsInDisplayTick. NumUnitsInDisplayTick is the number of time units of a clock operating at the frequency TimeScale Hz that
+ /// corresponds to one increment of a clock tick counter. A display clock tick, in seconds, is equal to
+ /// NumUnitsInDisplayTick divided by TimeScale.
+ ///
+ public uint NumUnitsInDisplayTick { get; set; }
+
+ ///
+ /// Gets or sets TimeScale. TimeScale is the number of time units that pass in one second.
+ /// It is a requirement of bitstream conformance that TimeScale is greater than 0.
+ ///
+ public uint TimeScale { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether that pictures should be displayed according to their output order with the
+ /// number of ticks between two consecutive pictures (without dropping frames) specified by NumTicksPerPicture.
+ /// EqualPictureInterval equal to false indicates that the interval between two consecutive pictures is not specified.
+ ///
+ public bool EqualPictureInterval { get; set; }
+
+ ///
+ /// Gets or sets NumTicksPerPicture. NumTicksPerPicture specifies the number of clock ticks corresponding to output time between two
+ /// consecutive pictures in the output order.
+ ///
+ public uint NumTicksPerPicture { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs
new file mode 100644
index 0000000000..0fa71e5922
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal enum ObuTransferCharacteristics
+{
+ Bt709 = 1,
+ Unspecified = 2,
+ Bt470M = 4,
+ Bt470BG = 5,
+ Bt601 = 6,
+ Smpte240 = 7,
+ Linear = 8,
+ Log100 = 9,
+ Log100Sqrt10 = 10,
+ Iec61966 = 11,
+ Bt1361 = 12,
+ Srgb = 13,
+ Bt202010Bit = 14,
+ Bt202012Bit = 15,
+ Smpte2084 = 16,
+ Smpte248 = 17,
+ Hlg = 18,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs
new file mode 100644
index 0000000000..34285ef2f0
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal enum ObuType
+{
+ None = 0,
+ SequenceHeader = 1,
+ TemporalDelimiter = 2,
+ FrameHeader = 3,
+ RedundantFrameHeader = 7,
+ TileGroup = 4,
+ Metadata = 5,
+ Frame = 6,
+ TileList = 8,
+ Padding = 15,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
new file mode 100644
index 0000000000..f718eedd6e
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
@@ -0,0 +1,862 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuWriter
+{
+ private int[] previousQIndex = [];
+ private int[] previousDeltaLoopFilter = [];
+
+ ///
+ /// Encode a single frame into OBU's.
+ ///
+ public void WriteAll(Configuration configuration, Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1TileWriter tileWriter)
+ {
+ // TODO: Determine inital size dynamically
+ int initialBufferSize = 2000;
+ AutoExpandingMemory buffer = new(configuration, initialBufferSize);
+ Av1BitStreamWriter writer = new(buffer);
+ WriteObuHeaderAndSize(stream, ObuType.TemporalDelimiter, []);
+
+ if (sequenceHeader != null)
+ {
+ WriteSequenceHeader(ref writer, sequenceHeader);
+ int bytesWritten = (writer.BitPosition + 7) >> 3;
+ writer.Flush();
+ WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, buffer.GetSpan(bytesWritten));
+ }
+
+ if (frameHeader != null && sequenceHeader != null)
+ {
+ this.WriteFrameHeader(ref writer, sequenceHeader, frameHeader, false);
+ if (frameHeader.TilesInfo != null)
+ {
+ WriteTileGroup(ref writer, frameHeader.TilesInfo, tileWriter);
+ }
+
+ int bytesWritten = (writer.BitPosition + 7) >> 3;
+ writer.Flush();
+ WriteObuHeaderAndSize(stream, ObuType.Frame, buffer.GetSpan(bytesWritten));
+ }
+ }
+
+ private static void WriteObuHeader(ref Av1BitStreamWriter writer, ObuType type)
+ {
+ writer.WriteBoolean(false); // Forbidden bit
+ writer.WriteLiteral((uint)type, 4);
+ writer.WriteBoolean(false); // Extension
+ writer.WriteBoolean(true); // HasSize
+ writer.WriteBoolean(false); // Reserved
+ }
+
+ private static byte WriteObuHeader(ObuType type) =>
+
+ // 0: Forbidden bit
+ // 1: Type, 4
+ // 5: Extension (false)
+ // 6: HasSize (true)
+ // 7: Reserved (false)
+ (byte)(((byte)type << 3) | 0x02);
+
+ ///
+ /// Read OBU header and size.
+ ///
+ private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span payload)
+ {
+ stream.WriteByte(WriteObuHeader(type));
+ Span lengthBytes = stackalloc byte[3];
+ int lengthLength = Av1BitStreamWriter.GetLittleEndianBytes128((uint)payload.Length, lengthBytes);
+ stream.Write(lengthBytes, 0, lengthLength);
+ stream.Write(payload);
+ }
+
+ ///
+ /// Write trsainling bits to end on a byte boundary, these trailing bits start with a 1 and end with 0s.
+ ///
+ /// Write an additional byte, if already byte aligned before.
+ private static void WriteTrailingBits(ref Av1BitStreamWriter writer)
+ {
+ int bitsBeforeAlignment = 8 - (writer.BitPosition & 0x7);
+ if (bitsBeforeAlignment != 8)
+ {
+ writer.WriteLiteral(1U << (bitsBeforeAlignment - 1), bitsBeforeAlignment);
+ }
+ }
+
+ private static void AlignToByteBoundary(ref Av1BitStreamWriter writer)
+ {
+ while ((writer.BitPosition & 0x7) > 0)
+ {
+ writer.WriteBoolean(false);
+ }
+ }
+
+ private static bool IsValidObuType(ObuType type) => type switch
+ {
+ ObuType.SequenceHeader or ObuType.TemporalDelimiter or ObuType.FrameHeader or
+ ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrameHeader or
+ ObuType.TileList or ObuType.Padding => true,
+ _ => false,
+ };
+
+ private static void WriteSequenceHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader)
+ {
+ writer.WriteLiteral((uint)sequenceHeader.SequenceProfile, 3);
+ writer.WriteBoolean(true); // IsStillPicture
+ writer.WriteBoolean(true); // IsReducedStillPicture
+ writer.WriteLiteral((uint)sequenceHeader.OperatingPoint[0].SequenceLevelIndex, Av1Constants.LevelBits);
+
+ // Frame width and Height
+ writer.WriteLiteral((uint)sequenceHeader.FrameWidthBits - 1, 4);
+ writer.WriteLiteral((uint)sequenceHeader.FrameHeightBits - 1, 4);
+ writer.WriteLiteral((uint)sequenceHeader.MaxFrameWidth - 1, sequenceHeader.FrameWidthBits);
+ writer.WriteLiteral((uint)sequenceHeader.MaxFrameHeight - 1, sequenceHeader.FrameHeightBits);
+
+ // Video related flags removed
+ writer.WriteBoolean(sequenceHeader.Use128x128Superblock);
+ writer.WriteBoolean(sequenceHeader.EnableFilterIntra);
+ writer.WriteBoolean(sequenceHeader.EnableIntraEdgeFilter);
+
+ // Video related flags removed
+ writer.WriteBoolean(sequenceHeader.EnableSuperResolution);
+ writer.WriteBoolean(sequenceHeader.EnableCdef);
+ writer.WriteBoolean(sequenceHeader.EnableRestoration);
+ WriteColorConfig(ref writer, sequenceHeader);
+ writer.WriteBoolean(sequenceHeader.AreFilmGrainingParametersPresent);
+ WriteTrailingBits(ref writer);
+ }
+
+ private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader)
+ {
+ ObuColorConfig colorConfig = sequenceHeader.ColorConfig;
+ WriteBitDepth(ref writer, colorConfig, sequenceHeader);
+ if (sequenceHeader.SequenceProfile != ObuSequenceProfile.High)
+ {
+ writer.WriteBoolean(colorConfig.IsMonochrome);
+ }
+
+ writer.WriteBoolean(colorConfig.IsColorDescriptionPresent);
+ if (colorConfig.IsColorDescriptionPresent)
+ {
+ writer.WriteLiteral((uint)colorConfig.ColorPrimaries, 8);
+ writer.WriteLiteral((uint)colorConfig.TransferCharacteristics, 8);
+ writer.WriteLiteral((uint)colorConfig.MatrixCoefficients, 8);
+ }
+
+ if (colorConfig.IsMonochrome)
+ {
+ writer.WriteBoolean(colorConfig.ColorRange);
+ return;
+ }
+ else if (
+ colorConfig.ColorPrimaries == ObuColorPrimaries.Bt709 &&
+ colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb &&
+ colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity)
+ {
+ colorConfig.ColorRange = true;
+ colorConfig.SubSamplingX = false;
+ colorConfig.SubSamplingY = false;
+ }
+ else
+ {
+ writer.WriteBoolean(colorConfig.ColorRange);
+ if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && colorConfig.BitDepth == Av1BitDepth.TwelveBit)
+ {
+ writer.WriteBoolean(colorConfig.SubSamplingX);
+ if (colorConfig.SubSamplingX)
+ {
+ writer.WriteBoolean(colorConfig.SubSamplingY);
+ }
+ }
+
+ if (colorConfig.SubSamplingX && colorConfig.SubSamplingY)
+ {
+ writer.WriteLiteral((uint)colorConfig.ChromaSamplePosition, 2);
+ }
+ }
+
+ writer.WriteBoolean(colorConfig.HasSeparateUvDelta);
+ }
+
+ private static void WriteBitDepth(ref Av1BitStreamWriter writer, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader)
+ {
+ bool hasHighBitDepth = colorConfig.BitDepth > Av1BitDepth.EightBit;
+ writer.WriteBoolean(hasHighBitDepth);
+ if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && hasHighBitDepth)
+ {
+ writer.WriteBoolean(colorConfig.BitDepth == Av1BitDepth.TwelveBit);
+ }
+ }
+
+ private static void WriteSuperResolutionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ bool useSuperResolution = false;
+ if (sequenceHeader.EnableSuperResolution)
+ {
+ writer.WriteBoolean(useSuperResolution);
+ }
+
+ if (useSuperResolution)
+ {
+ writer.WriteLiteral((uint)frameHeader.FrameSize.SuperResolutionDenominator - Av1Constants.SuperResolutionScaleDenominatorMinimum, Av1Constants.SuperResolutionScaleBits);
+ }
+ }
+
+ private static void WriteRenderSize(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
+ {
+ bool renderSizeAndFrameSizeDifferent = false;
+ writer.WriteBoolean(false);
+ if (renderSizeAndFrameSizeDifferent)
+ {
+ writer.WriteLiteral((uint)frameHeader.FrameSize.RenderWidth - 1, 16);
+ writer.WriteLiteral((uint)frameHeader.FrameSize.RenderHeight - 1, 16);
+ }
+ }
+
+ private static void WriteFrameSizeWithReferences(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool frameSizeOverrideFlag)
+ {
+ bool foundReference = false;
+ for (int i = 0; i < Av1Constants.ReferencesPerFrame; i++)
+ {
+ writer.WriteBoolean(foundReference);
+ if (foundReference)
+ {
+ // Take values over from reference frame
+ break;
+ }
+ }
+
+ if (!foundReference)
+ {
+ WriteFrameSize(ref writer, sequenceHeader, frameHeader, frameSizeOverrideFlag);
+ WriteRenderSize(ref writer, frameHeader);
+ }
+ else
+ {
+ WriteSuperResolutionParameters(ref writer, sequenceHeader, frameHeader);
+ }
+ }
+
+ private static void WriteFrameSize(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool frameSizeOverrideFlag)
+ {
+ if (frameSizeOverrideFlag)
+ {
+ writer.WriteLiteral((uint)frameHeader.FrameSize.FrameWidth - 1, sequenceHeader.FrameWidthBits + 1);
+ writer.WriteLiteral((uint)frameHeader.FrameSize.FrameHeight - 1, sequenceHeader.FrameHeightBits + 1);
+ }
+
+ WriteSuperResolutionParameters(ref writer, sequenceHeader, frameHeader);
+ }
+
+ private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ ObuTileGroupHeader tileInfo = frameHeader.TilesInfo;
+ int superblockColumnCount;
+ int superblockRowCount;
+ int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2;
+ int superblockShift = superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2;
+ superblockColumnCount = (frameHeader.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift;
+ superblockRowCount = (frameHeader.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift;
+ int superBlockSize = superblockShift + 2;
+ int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSize);
+
+ tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superBlockSize;
+ tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSize;
+ tileInfo.MinLog2TileColumnCount = ObuReader.TileLog2(tileInfo.MaxTileWidthSuperblock, superblockColumnCount);
+ tileInfo.MaxLog2TileColumnCount = ObuReader.TileLog2(1, Math.Min(superblockColumnCount, Av1Constants.MaxTileColumnCount));
+ tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superblockRowCount, Av1Constants.MaxTileRowCount));
+ tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, ObuReader.TileLog2(maxTileAreaOfSuperBlock, superblockColumnCount * superblockRowCount));
+
+ int log2TileColumnCount = Av1Math.Log2(tileInfo.TileColumnCount);
+ int log2TileRowCount = Av1Math.Log2(tileInfo.TileRowCount);
+
+ writer.WriteBoolean(tileInfo.HasUniformTileSpacing);
+ if (tileInfo.HasUniformTileSpacing)
+ {
+ // Uniform spaced tiles with power-of-two number of rows and columns
+ // tile columns
+ int ones = log2TileColumnCount - tileInfo.MinLog2TileColumnCount;
+ while (ones-- > 0)
+ {
+ writer.WriteBoolean(true);
+ }
+
+ if (log2TileColumnCount < tileInfo.MaxLog2TileColumnCount)
+ {
+ writer.WriteBoolean(false);
+ }
+
+ // rows
+ tileInfo.MinLog2TileRowCount = Math.Min(tileInfo.MinLog2TileCount - log2TileColumnCount, 0);
+ ones = log2TileRowCount - tileInfo.MinLog2TileRowCount;
+ while (ones-- > 0)
+ {
+ writer.WriteBoolean(true);
+ }
+
+ if (log2TileRowCount < tileInfo.MaxLog2TileRowCount)
+ {
+ writer.WriteBoolean(false);
+ }
+ }
+ else
+ {
+ int startSuperBlock = 0;
+ int i = 0;
+ for (; startSuperBlock < superblockColumnCount; i++)
+ {
+ uint widthInSuperBlocks = (uint)((tileInfo.TileColumnStartModeInfo[i] >> superblockShift) - startSuperBlock);
+ uint maxWidth = (uint)Math.Min(superblockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperblock);
+ writer.WriteNonSymmetric(widthInSuperBlocks - 1, maxWidth);
+ startSuperBlock += (int)widthInSuperBlocks;
+ }
+
+ if (startSuperBlock != superblockColumnCount)
+ {
+ throw new ImageFormatException("Super block tiles width does not add up to total width.");
+ }
+
+ startSuperBlock = 0;
+ for (i = 0; startSuperBlock < superblockRowCount; i++)
+ {
+ uint heightInSuperBlocks = (uint)((tileInfo.TileRowStartModeInfo[i] >> superblockShift) - startSuperBlock);
+ uint maxHeight = (uint)Math.Min(superblockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperblock);
+ writer.WriteNonSymmetric(heightInSuperBlocks - 1, maxHeight);
+ startSuperBlock += (int)heightInSuperBlocks;
+ }
+
+ if (startSuperBlock != superblockRowCount)
+ {
+ throw new ImageFormatException("Super block tiles height does not add up to total height.");
+ }
+ }
+
+ if (tileInfo.TileColumnCountLog2 > 0 || tileInfo.TileRowCountLog2 > 0)
+ {
+ writer.WriteLiteral(tileInfo.ContextUpdateTileId, tileInfo.TileRowCountLog2 + tileInfo.TileColumnCountLog2);
+ writer.WriteLiteral((uint)tileInfo.TileSizeBytes - 1, 2);
+ }
+
+ frameHeader.TilesInfo = tileInfo;
+ }
+
+ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ // TODO: Make tile count configurable.
+ int tileCount = 1;
+ int planesCount = sequenceHeader.ColorConfig.PlaneCount;
+ writer.WriteBoolean(frameHeader.DisableCdfUpdate);
+ if (sequenceHeader.ForceScreenContentTools == 2)
+ {
+ writer.WriteBoolean(frameHeader.AllowScreenContentTools);
+ }
+ else
+ {
+ // Guard.IsTrue(frameHeader.AllowScreenContentTools == sequenceHeader.ForceScreenContentTools);
+ }
+
+ if (frameHeader.AllowScreenContentTools)
+ {
+ if (sequenceHeader.ForceIntegerMotionVector == 2)
+ {
+ writer.WriteBoolean(frameHeader.ForceIntegerMotionVector);
+ }
+ else
+ {
+ // Guard.IsTrue(frameHeader.ForceIntegerMotionVector == sequenceHeader.ForceIntegerMotionVector, nameof(frameHeader.ForceIntegerMotionVector), "Frame and sequence must be in sync");
+ }
+ }
+
+ if (frameHeader.FrameType == ObuFrameType.KeyFrame)
+ {
+ if (!frameHeader.ShowFrame)
+ {
+ throw new NotImplementedException("No support for hidden frames.");
+ }
+ }
+ else if (frameHeader.FrameType == ObuFrameType.IntraOnlyFrame)
+ {
+ throw new NotImplementedException("No IntraOnly frames supported.");
+ }
+
+ if (frameHeader.FrameType == ObuFrameType.KeyFrame)
+ {
+ WriteFrameSize(ref writer, sequenceHeader, frameHeader, false);
+ WriteRenderSize(ref writer, frameHeader);
+ if (frameHeader.AllowScreenContentTools)
+ {
+ writer.WriteBoolean(frameHeader.AllowIntraBlockCopy);
+ }
+ }
+ else if (frameHeader.FrameType == ObuFrameType.IntraOnlyFrame)
+ {
+ WriteFrameSize(ref writer, sequenceHeader, frameHeader, false);
+ WriteRenderSize(ref writer, frameHeader);
+ if (frameHeader.AllowScreenContentTools)
+ {
+ writer.WriteBoolean(frameHeader.AllowIntraBlockCopy);
+ }
+ }
+ else
+ {
+ throw new NotImplementedException("Inter frames not applicable for AVIF.");
+ }
+
+ WriteTileInfo(ref writer, sequenceHeader, frameHeader);
+ WriteQuantizationParameters(ref writer, sequenceHeader, frameHeader);
+ WriteSegmentationParameters(ref writer, sequenceHeader, frameHeader);
+
+ if (frameHeader.QuantizationParameters.BaseQIndex > 0)
+ {
+ writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent);
+ if (frameHeader.DeltaQParameters.IsPresent)
+ {
+ writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution - 1, 2);
+ this.previousQIndex = new int[tileCount];
+ for (int tileIndex = 0; tileIndex < tileCount; tileIndex++)
+ {
+ this.previousQIndex[tileIndex] = frameHeader.QuantizationParameters.BaseQIndex;
+ }
+
+ if (frameHeader.AllowIntraBlockCopy)
+ {
+ Guard.IsFalse(
+ frameHeader.DeltaLoopFilterParameters.IsPresent,
+ nameof(frameHeader.DeltaLoopFilterParameters.IsPresent),
+ "Allow INTRA block copy required Loop Filter.");
+ }
+ else
+ {
+ writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsPresent);
+ }
+
+ if (frameHeader.DeltaLoopFilterParameters.IsPresent)
+ {
+ writer.WriteLiteral((uint)(1 + Av1Math.MostSignificantBit((uint)frameHeader.DeltaLoopFilterParameters.Resolution) - 1), 2);
+ writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti);
+ int frameLoopFilterCount = sequenceHeader.ColorConfig.IsMonochrome ? Av1Constants.FrameLoopFilterCount - 2 : Av1Constants.FrameLoopFilterCount;
+ this.previousDeltaLoopFilter = new int[frameLoopFilterCount];
+ for (int loopFilterId = 0; loopFilterId < frameLoopFilterCount; loopFilterId++)
+ {
+ this.previousDeltaLoopFilter[loopFilterId] = 0;
+ }
+ }
+ }
+ }
+
+ if (frameHeader.AllLossless)
+ {
+ throw new NotImplementedException("No entire lossless supported.");
+ }
+ else
+ {
+ if (!frameHeader.CodedLossless)
+ {
+ WriteLoopFilterParameters(ref writer, sequenceHeader, frameHeader);
+ if (sequenceHeader.CdefLevel > 0)
+ {
+ WriteCdefParameters(ref writer, sequenceHeader, frameHeader);
+ }
+ }
+
+ if (sequenceHeader.EnableRestoration)
+ {
+ WriteLoopRestorationParameters(ref writer, sequenceHeader, frameHeader);
+ }
+ }
+
+ // No Frame Reference mode selection for AVIF
+ WriteTransformMode(ref writer, frameHeader);
+
+ // No compound INTER-INTER for AVIF.
+ WriteFrameReferenceMode(ref writer, frameHeader);
+ WriteSkipModeParameters(ref writer, frameHeader);
+
+ // No warp motion for AVIF.
+ writer.WriteBoolean(frameHeader.UseReducedTransformSet);
+
+ WriteGlobalMotionParameters(ref writer, frameHeader);
+ WriteFilmGrainFilterParameters(ref writer, sequenceHeader, frameHeader);
+ }
+
+ private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature)
+ => segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature];
+
+ private int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool writeTrailingBits)
+ {
+ int startBitPosition = writer.BitPosition;
+ this.WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameHeader);
+ if (writeTrailingBits)
+ {
+ WriteTrailingBits(ref writer);
+ }
+
+ int endPosition = writer.BitPosition;
+ int headerBytes = (endPosition - startBitPosition) / 8;
+ return headerBytes;
+ }
+
+ ///
+ /// 5.11.1. General tile group OBU syntax.
+ ///
+ private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter)
+ {
+ int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
+ int startBitPosition = writer.BitPosition;
+ bool tileStartAndEndPresentFlag = tileCount > 1;
+ writer.WriteBoolean(tileStartAndEndPresentFlag);
+
+ uint tileGroupStart = 0U;
+ uint tileGroupEnd = (uint)tileCount - 1U;
+ if (tileCount != 1)
+ {
+ int tileBits = Av1Math.Log2(tileInfo.TileColumnCount) + Av1Math.Log2(tileInfo.TileRowCount);
+ writer.WriteLiteral(tileGroupStart, tileBits);
+ writer.WriteLiteral(tileGroupEnd, tileBits);
+ }
+
+ AlignToByteBoundary(ref writer);
+
+ WriteTileData(ref writer, tileInfo, tileWriter);
+
+ int endBitPosition = writer.BitPosition;
+ int headerBytes = (endBitPosition - startBitPosition) / 8;
+ return headerBytes;
+ }
+
+ private static void WriteTileData(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter)
+ {
+ int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
+ for (int tileNum = 0; tileNum < tileCount; tileNum++)
+ {
+ Span tileData = tileWriter.WriteTile(tileNum);
+ if (tileNum != tileCount - 1 && tileCount > 1)
+ {
+ writer.WriteLittleEndian((uint)tileData.Length - 1U, tileInfo.TileSizeBytes);
+ }
+
+ writer.WriteBlob(tileData);
+ }
+ }
+
+ private static int WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ)
+ {
+ bool isCoded = deltaQ != 0;
+ writer.WriteBoolean(isCoded);
+ if (isCoded)
+ {
+ writer.WriteSignedFromUnsigned(deltaQ, 7);
+ }
+
+ return deltaQ;
+ }
+
+ private static void WriteFrameDeltaQParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
+ {
+ if (frameHeader.QuantizationParameters.BaseQIndex > 0)
+ {
+ writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent);
+ }
+
+ if (frameHeader.DeltaQParameters.IsPresent)
+ {
+ writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution, 2);
+ }
+ }
+
+ private static void WriteFrameDeltaLoopFilterParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
+ {
+ if (frameHeader.DeltaQParameters.IsPresent)
+ {
+ if (!frameHeader.AllowIntraBlockCopy)
+ {
+ writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsPresent);
+ }
+
+ if (frameHeader.DeltaLoopFilterParameters.IsPresent)
+ {
+ writer.WriteLiteral((uint)frameHeader.DeltaLoopFilterParameters.Resolution, 2);
+ writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti);
+ }
+ }
+ }
+
+ ///
+ /// See section 5.9.12.
+ ///
+ private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ ObuQuantizationParameters quantParams = frameHeader.QuantizationParameters;
+ writer.WriteLiteral((uint)quantParams.BaseQIndex, 8);
+ WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.Y]);
+ if (sequenceHeader.ColorConfig.PlaneCount > 1)
+ {
+ if (sequenceHeader.ColorConfig.HasSeparateUvDelta)
+ {
+ writer.WriteBoolean(quantParams.HasSeparateUvDelta);
+ }
+
+ WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.U]);
+ WriteDeltaQ(ref writer, quantParams.DeltaQAc[(int)Av1Plane.U]);
+ if (quantParams.HasSeparateUvDelta)
+ {
+ WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.V]);
+ WriteDeltaQ(ref writer, quantParams.DeltaQAc[(int)Av1Plane.V]);
+ }
+ }
+
+ writer.WriteBoolean(quantParams.IsUsingQMatrix);
+ if (quantParams.IsUsingQMatrix)
+ {
+ writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.Y], 4);
+ writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.U], 4);
+ if (sequenceHeader.ColorConfig.HasSeparateUvDelta)
+ {
+ writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.V], 4);
+ }
+ }
+ }
+
+ private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ _ = sequenceHeader;
+ Guard.IsFalse(frameHeader.SegmentationParameters.Enabled, nameof(frameHeader.SegmentationParameters.Enabled), "Segmentation not supported yet.");
+ writer.WriteBoolean(false);
+ }
+
+ ///
+ /// 5.9.11. Loop filter params syntax
+ ///
+ private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy)
+ {
+ return;
+ }
+
+ writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevel[0], 6);
+ writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevel[1], 6);
+ if (sequenceHeader.ColorConfig.PlaneCount > 1)
+ {
+ if (frameHeader.LoopFilterParameters.FilterLevel[0] > 0 || frameHeader.LoopFilterParameters.FilterLevel[1] > 0)
+ {
+ writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevelU, 6);
+ writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevelV, 6);
+ }
+ }
+
+ writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.SharpnessLevel, 3);
+ writer.WriteBoolean(frameHeader.LoopFilterParameters.ReferenceDeltaModeEnabled);
+ if (frameHeader.LoopFilterParameters.ReferenceDeltaModeEnabled)
+ {
+ writer.WriteBoolean(frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate);
+ if (frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate)
+ {
+ throw new NotImplementedException("Reference update of loop filter not supported yet.");
+ }
+ }
+ }
+
+ ///
+ /// 5.9.21. TX mode syntax.
+ ///
+ private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
+ {
+ if (!frameHeader.CodedLossless)
+ {
+ writer.WriteBoolean(frameHeader.TransformMode == Av1TransformMode.Select);
+ }
+ }
+
+ private static void WriteLoopRestorationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration)
+ {
+ return;
+ }
+
+ int planesCount = sequenceHeader.ColorConfig.PlaneCount;
+ for (int i = 0; i < planesCount; i++)
+ {
+ writer.WriteLiteral((uint)frameHeader.LoopRestorationParameters.Items[i].Type, 2);
+ }
+
+ if (frameHeader.LoopRestorationParameters.UsesLoopRestoration)
+ {
+ uint unitShift = (uint)frameHeader.LoopRestorationParameters.UnitShift;
+ if (sequenceHeader.Use128x128Superblock)
+ {
+ writer.WriteLiteral(unitShift - 1, 1);
+ }
+ else
+ {
+ writer.WriteLiteral(unitShift & 0x01, 1);
+ if (unitShift > 0)
+ {
+ writer.WriteLiteral(unitShift - 1, 1);
+ }
+ }
+
+ if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && frameHeader.LoopRestorationParameters.UsesChromaLoopRestoration)
+ {
+ writer.WriteLiteral((uint)frameHeader.LoopRestorationParameters.UVShift, 1);
+ }
+ }
+ }
+
+ private static void WriteCdefParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ _ = writer;
+ _ = sequenceHeader;
+
+ if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableCdef)
+ {
+ return;
+ }
+
+ throw new NotImplementedException("Didn't implement writing CDEF yet.");
+ }
+
+ private static void WriteGlobalMotionParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
+ {
+ _ = writer;
+
+ if (frameHeader.IsIntra)
+ {
+ // Nothing to be written for INTRA frames.
+ return;
+ }
+
+ throw new InvalidImageContentException("AVIF files can only contain INTRA frames.");
+ }
+
+ private static void WriteFrameReferenceMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
+ {
+ _ = writer;
+
+ if (frameHeader.IsIntra)
+ {
+ // Nothing to be written for INTRA frames.
+ return;
+ }
+
+ throw new InvalidImageContentException("AVIF files can only contain INTRA frames.");
+ }
+
+ private static void WriteSkipModeParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
+ {
+ if (frameHeader.SkipModeParameters.SkipModeAllowed)
+ {
+ writer.WriteBoolean(frameHeader.SkipModeParameters.SkipModeFlag);
+ }
+ }
+
+ private static void WriteFilmGrainFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ ObuFilmGrainParameters grainParams = frameHeader.FilmGrainParameters;
+ if (!sequenceHeader.AreFilmGrainingParametersPresent || (!frameHeader.ShowFrame && !frameHeader.ShowableFrame))
+ {
+ return;
+ }
+
+ writer.WriteBoolean(grainParams.ApplyGrain);
+ if (!grainParams.ApplyGrain)
+ {
+ return;
+ }
+
+ writer.WriteLiteral(grainParams.GrainSeed, 16);
+ writer.WriteLiteral(grainParams.NumYPoints, 4);
+ Guard.NotNull(grainParams.PointYValue);
+ Guard.NotNull(grainParams.PointYScaling);
+ for (int i = 0; i < grainParams.NumYPoints; i++)
+ {
+ writer.WriteLiteral(grainParams.PointYValue[i], 8);
+ writer.WriteLiteral(grainParams.PointYScaling[i], 8);
+ }
+
+ if (!sequenceHeader.ColorConfig.IsMonochrome)
+ {
+ writer.WriteBoolean(grainParams.ChromaScalingFromLuma);
+ }
+
+ if (!sequenceHeader.ColorConfig.IsMonochrome &&
+ !grainParams.ChromaScalingFromLuma &&
+ (!sequenceHeader.ColorConfig.SubSamplingX || !sequenceHeader.ColorConfig.SubSamplingY || grainParams.NumYPoints != 0))
+ {
+ writer.WriteLiteral(grainParams.NumCbPoints, 4);
+ Guard.NotNull(grainParams.PointCbValue);
+ Guard.NotNull(grainParams.PointCbScaling);
+ for (int i = 0; i < grainParams.NumCbPoints; i++)
+ {
+ writer.WriteLiteral(grainParams.PointCbValue[i], 8);
+ writer.WriteLiteral(grainParams.PointCbScaling[i], 8);
+ }
+
+ writer.WriteLiteral(grainParams.NumCrPoints, 4);
+ Guard.NotNull(grainParams.PointCrValue);
+ Guard.NotNull(grainParams.PointCrScaling);
+ for (int i = 0; i < grainParams.NumCbPoints; i++)
+ {
+ writer.WriteLiteral(grainParams.PointCrValue[i], 8);
+ writer.WriteLiteral(grainParams.PointCrScaling[i], 8);
+ }
+ }
+
+ writer.WriteLiteral(grainParams.GrainScalingMinus8, 2);
+ writer.WriteLiteral(grainParams.ArCoeffLag, 2);
+ uint numPosLuma = 2 * grainParams.ArCoeffLag * (grainParams.ArCoeffLag + 1);
+
+ uint numPosChroma = 0;
+ if (grainParams.NumYPoints != 0)
+ {
+ numPosChroma = numPosLuma + 1;
+ Guard.NotNull(grainParams.ArCoeffsYPlus128);
+ for (int i = 0; i < numPosLuma; i++)
+ {
+ writer.WriteLiteral(grainParams.ArCoeffsYPlus128[i], 8);
+ }
+ }
+
+ if (grainParams.ChromaScalingFromLuma || grainParams.NumCbPoints != 0)
+ {
+ Guard.NotNull(grainParams.ArCoeffsCbPlus128);
+ for (int i = 0; i < numPosChroma; i++)
+ {
+ writer.WriteLiteral(grainParams.ArCoeffsCbPlus128[i], 8);
+ }
+ }
+
+ if (grainParams.ChromaScalingFromLuma || grainParams.NumCrPoints != 0)
+ {
+ Guard.NotNull(grainParams.ArCoeffsCrPlus128);
+ for (int i = 0; i < numPosChroma; i++)
+ {
+ writer.WriteLiteral(grainParams.ArCoeffsCrPlus128[i], 8);
+ }
+ }
+
+ writer.WriteLiteral(grainParams.ArCoeffShiftMinus6, 2);
+ writer.WriteLiteral(grainParams.GrainScaleShift, 2);
+ if (grainParams.NumCbPoints != 0)
+ {
+ writer.WriteLiteral(grainParams.CbMult, 8);
+ writer.WriteLiteral(grainParams.CbLumaMult, 8);
+ writer.WriteLiteral(grainParams.CbOffset, 9);
+ }
+
+ if (grainParams.NumCrPoints != 0)
+ {
+ writer.WriteLiteral(grainParams.CrMult, 8);
+ writer.WriteLiteral(grainParams.CrLumaMult, 8);
+ writer.WriteLiteral(grainParams.CrOffset, 9);
+ }
+
+ writer.WriteBoolean(grainParams.OverlapFlag);
+ writer.WriteBoolean(grainParams.ClipToRestrictedRange);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs
new file mode 100644
index 0000000000..365cd46a54
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs
@@ -0,0 +1,160 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline;
+
+internal class Av1FrameDecoder
+{
+ private readonly ObuSequenceHeader sequenceHeader;
+ private readonly ObuFrameHeader frameHeader;
+ private readonly Av1FrameInfo frameInfo;
+ private readonly Av1FrameBuffer frameBuffer;
+ private readonly Av1InverseQuantizer inverseQuantizer;
+ private readonly Av1DeQuantizationContext deQuants;
+ private readonly Av1BlockDecoder blockDecoder;
+
+ public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer)
+ {
+ this.sequenceHeader = sequenceHeader;
+ this.frameHeader = frameHeader;
+ this.frameInfo = frameInfo;
+ this.frameBuffer = frameBuffer;
+ this.inverseQuantizer = new(sequenceHeader, frameHeader);
+ this.deQuants = new();
+ this.blockDecoder = new(this.sequenceHeader, this.frameHeader, this.frameInfo, this.frameBuffer);
+ }
+
+ public void DecodeFrame()
+ {
+ for (int column = 0; column < this.frameHeader.TilesInfo.TileColumnCount; column++)
+ {
+ this.DecodeFrameTiles(column);
+ }
+
+ bool doLoopFilterFlag = false;
+ bool doLoopRestoration = false;
+ bool doUpscale = false;
+ this.DecodeLoopFilterForFrame(doLoopFilterFlag);
+ if (doLoopRestoration)
+ {
+ // LoopRestorationSaveBoundaryLines(false);
+ }
+
+ // DecodeCdef();
+ // SuperResolutionUpscaling(doUpscale);
+ if (doLoopRestoration && doUpscale)
+ {
+ // LoopRestorationSaveBoundaryLines(true);
+ }
+
+ // DecodeLoopRestoration(doLoopRestoration);
+ // PadPicture();
+ }
+
+ private void DecodeFrameTiles(int tileColumn)
+ {
+ int tileRowCount = this.frameHeader.TilesInfo.TileRowCount;
+ int tileCount = tileRowCount * this.frameHeader.TilesInfo.TileColumnCount;
+ for (int row = 0; row < tileRowCount; row++)
+ {
+ int superblockRowTileStart = this.frameHeader.TilesInfo.TileRowStartModeInfo[row] << Av1Constants.ModeInfoSizeLog2 >>
+ this.sequenceHeader.SuperblockSizeLog2;
+ int superblockRow = row + superblockRowTileStart;
+
+ int modeInfoRow = superblockRow << this.sequenceHeader.SuperblockSizeLog2 >> Av1Constants.ModeInfoSizeLog2;
+
+ // EbColorConfig* color_config = &dec_mod_ctxt->seq_header->color_config;
+ // svt_cfl_init(&dec_mod_ctxt->cfl_ctx, color_config);
+ this.DecodeTileRow(row, tileColumn, modeInfoRow, superblockRow);
+ }
+ }
+
+ private void DecodeTileRow(int tileRow, int tileColumn, int modeInfoRow, int superblockRow)
+ {
+ int superblockModeInfoSizeLog2 = this.sequenceHeader.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2;
+ int superblockRowTileStart = this.frameHeader.TilesInfo.TileRowStartModeInfo[tileRow] << Av1Constants.ModeInfoSizeLog2 >>
+ this.sequenceHeader.SuperblockSizeLog2;
+
+ int superblockRowInTile = superblockRow - superblockRowTileStart;
+
+ ObuTileGroupHeader tileInfo = this.frameHeader.TilesInfo;
+ for (int modeInfoColumn = tileInfo.TileColumnStartModeInfo[tileColumn]; modeInfoColumn < tileInfo.TileColumnStartModeInfo[tileColumn + 1];
+ modeInfoColumn += this.sequenceHeader.SuperblockModeInfoSize)
+ {
+ int superblockColumn = modeInfoColumn << Av1Constants.ModeInfoSizeLog2 >> this.sequenceHeader.SuperblockSizeLog2;
+
+ Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(new Point(superblockColumn, superblockRow));
+
+ Point modeInfoPosition = new(modeInfoColumn, modeInfoRow);
+ this.DecodeSuperblock(modeInfoPosition, superblockInfo, new Av1TileInfo(tileRow, tileColumn, this.frameHeader));
+ }
+ }
+
+ private void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
+ {
+ this.blockDecoder.UpdateSuperblock(superblockInfo);
+ this.inverseQuantizer.UpdateDequant(this.deQuants, superblockInfo);
+ this.DecodePartition(modeInfoPosition, superblockInfo, tileInfo);
+ }
+
+ private void DecodePartition(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
+ {
+ Av1BlockModeInfo modeInfo = superblockInfo.GetModeInfo(modeInfoPosition);
+
+ for (int i = 0; i < superblockInfo.BlockCount; i++)
+ {
+ Point subPosition = modeInfo.PositionInSuperblock;
+ Av1BlockSize subSize = modeInfo.BlockSize;
+ Point globalPosition = new(modeInfoPosition.X, modeInfoPosition.Y);
+ globalPosition.Offset(subPosition);
+ this.blockDecoder.DecodeBlock(modeInfo, globalPosition, subSize, superblockInfo, tileInfo);
+ }
+ }
+
+ private void DecodeLoopFilterForFrame(bool doLoopFilterFlag)
+ {
+ if (!doLoopFilterFlag)
+ {
+ return;
+ }
+
+ int superblockSizeLog2 = this.sequenceHeader.SuperblockSizeLog2;
+ int pictureWidthInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameWidth, this.sequenceHeader.SuperblockSizeLog2);
+ int pictureHeightInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameHeight, this.sequenceHeader.SuperblockSizeLog2);
+
+ // Loop over a frame : tregger dec_loop_filter_sb for each SB
+ for (int superblockIndexY = 0; superblockIndexY < pictureHeightInSuperblocks; ++superblockIndexY)
+ {
+ for (int superblockIndexX = 0; superblockIndexX < pictureWidthInSuperblocks; ++superblockIndexX)
+ {
+ int superblockOriginX = superblockIndexX << superblockSizeLog2;
+ int superblockOriginY = superblockIndexY << superblockSizeLog2;
+ bool endOfRowFlag = superblockIndexX == pictureWidthInSuperblocks - 1;
+
+ Point superblockPoint = new(superblockOriginX, superblockOriginY);
+ Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(superblockPoint);
+
+ // LF function for a SB
+ /*
+ DecodeLoopFilterForSuperblock(
+ superblockInfo,
+ this.frameHeader,
+ this.sequenceHeader,
+ reconstructionFrameBuffer,
+ loopFilterContext,
+ superblockOriginY >> 2,
+ superblockOriginX >> 2,
+ Av1Plane.Y,
+ 3,
+ endOfRowFlag,
+ superblockInfo.SuperblockDeltaLoopFilter);
+ */
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs
new file mode 100644
index 0000000000..91b036ff6a
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs
@@ -0,0 +1,8 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter;
+
+internal class Av1LoopFilterContext
+{
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs
new file mode 100644
index 0000000000..9a895ea7e5
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter;
+
+internal class Av1LoopFilterDecoder
+{
+ private readonly ObuSequenceHeader sequenceHeader;
+ private readonly ObuFrameHeader frameHeader;
+ private readonly Av1FrameInfo frameInfo;
+ private readonly Av1FrameBuffer frameBuffer;
+ private readonly Av1LoopFilterContext loopFilterContext;
+
+ public Av1LoopFilterDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer)
+ {
+ this.sequenceHeader = sequenceHeader;
+ this.frameHeader = frameHeader;
+ this.frameInfo = frameInfo;
+ this.frameBuffer = frameBuffer;
+ this.loopFilterContext = new();
+ }
+
+ public void DecodeFrame(bool doLoopFilterFlag)
+ {
+ Guard.NotNull(this.sequenceHeader);
+ Guard.NotNull(this.frameHeader);
+ Guard.NotNull(this.frameInfo);
+
+ if (!doLoopFilterFlag)
+ {
+ return;
+ }
+
+ int superblockSizeLog2 = this.sequenceHeader.SuperblockSizeLog2;
+ int frameWidthInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameWidth, this.sequenceHeader.SuperblockSizeLog2);
+ int frameHeightInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameHeight, this.sequenceHeader.SuperblockSizeLog2);
+
+ // Loop over a frame : tregger dec_loop_filter_sb for each SB
+ for (int superblockIndexY = 0; superblockIndexY < frameHeightInSuperblocks; ++superblockIndexY)
+ {
+ for (int superblockIndexX = 0; superblockIndexX < frameWidthInSuperblocks; ++superblockIndexX)
+ {
+ int superblockOriginX = superblockIndexX << superblockSizeLog2;
+ int superblockOriginY = superblockIndexY << superblockSizeLog2;
+ bool endOfRowFlag = superblockIndexX == frameWidthInSuperblocks - 1;
+
+ Point superblockPoint = new(superblockOriginX, superblockOriginY);
+ Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(superblockPoint);
+ Point superblockOriginInModeInfo = new(superblockOriginX >> 2, superblockOriginY >> 2);
+
+ // LF function for a SB
+ this.DecodeForSuperblock(
+ superblockInfo,
+ superblockOriginInModeInfo,
+ Av1Plane.Y,
+ 3,
+ endOfRowFlag,
+ superblockInfo.SuperblockDeltaLoopFilter);
+ }
+ }
+ }
+
+ private void DecodeForSuperblock(Av1SuperblockInfo superblockInfo, Point modeInfoLocation, Av1Plane startPlane, int endPlane, bool endOfRowFlag, Span superblockDeltaLoopFilter)
+ => throw new NotImplementedException();
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs
new file mode 100644
index 0000000000..304b72cdf5
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
+
+internal class Av1DeQuantizationContext
+{
+ private readonly short[][] dcContent;
+ private readonly short[][] acContent;
+
+ public Av1DeQuantizationContext()
+ {
+ this.dcContent = new short[Av1Constants.MaxSegmentCount][];
+ this.acContent = new short[Av1Constants.MaxSegmentCount][];
+ for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++)
+ {
+ this.dcContent[segmentId] = new short[Av1Constants.MaxPlanes];
+ this.acContent[segmentId] = new short[Av1Constants.MaxPlanes];
+ }
+ }
+
+ public short GetDc(int segmentId, Av1Plane plane)
+ => this.dcContent[segmentId][(int)plane];
+
+ public short GetAc(int segmentId, Av1Plane plane)
+ => this.acContent[segmentId][(int)plane];
+
+ public void SetAc(int segmentId, Av1Plane plane, short value)
+ => this.dcContent[segmentId][(int)plane] = value;
+
+ public void SetDc(int segmentId, Av1Plane plane, short value)
+ => this.dcContent[segmentId][(int)plane] = value;
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs
new file mode 100644
index 0000000000..e06e9ba0f6
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs
@@ -0,0 +1,146 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
+
+internal class Av1InverseQuantizer
+{
+ private const int QuatizationMatrixTotalSize = 3344;
+
+ private readonly ObuSequenceHeader sequenceHeader;
+ private readonly ObuFrameHeader frameHeader;
+ private readonly int[][][] inverseQuantizationMatrix;
+ private Av1DeQuantizationContext? deQuantsDeltaQ;
+
+ public Av1InverseQuantizer(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ this.sequenceHeader = sequenceHeader;
+ this.frameHeader = frameHeader;
+
+ this.inverseQuantizationMatrix = new int[Av1Constants.QuantificationMatrixLevelCount][][];
+ for (int q = 0; q < Av1Constants.QuantificationMatrixLevelCount; ++q)
+ {
+ this.inverseQuantizationMatrix[q] = new int[Av1Constants.MaxPlanes][];
+ for (int c = 0; c < Av1Constants.MaxPlanes; c++)
+ {
+ int lumaOrChroma = Math.Min(1, c);
+ int current = 0;
+ this.inverseQuantizationMatrix[q][c] = new int[(int)Av1TransformSize.AllSizes];
+ for (Av1TransformSize t = 0; t < Av1TransformSize.AllSizes; ++t)
+ {
+ int size = t.GetSize2d();
+ Av1TransformSize qmTransformSize = t.GetAdjusted();
+ if (q == Av1Constants.QuantificationMatrixLevelCount - 1)
+ {
+ this.inverseQuantizationMatrix[q][c][(int)t] = -1;
+ }
+ else if (t != qmTransformSize)
+ {
+ // Reuse matrices for 'qm_tx_size'
+ this.inverseQuantizationMatrix[q][c][(int)t] = this.inverseQuantizationMatrix[q][c][(int)qmTransformSize];
+ }
+ else
+ {
+ Guard.MustBeLessThanOrEqualTo(current + size, QuatizationMatrixTotalSize, nameof(current));
+ this.inverseQuantizationMatrix[q][c][(int)t] = Av1QuantizationConstants.InverseWT[q][lumaOrChroma][current];
+ current += size;
+ }
+ }
+ }
+ }
+ }
+
+ public void UpdateDequant(Av1DeQuantizationContext deQuants, Av1SuperblockInfo superblockInfo)
+ {
+ Av1BitDepth bitDepth = this.sequenceHeader.ColorConfig.BitDepth;
+ Guard.NotNull(deQuants, nameof(deQuants));
+ this.deQuantsDeltaQ = deQuants;
+ if (this.frameHeader.DeltaQParameters.IsPresent)
+ {
+ for (int i = 0; i < Av1Constants.MaxSegmentCount; i++)
+ {
+ int currentQIndex = Av1QuantizationLookup.GetQIndex(this.frameHeader.SegmentationParameters, i, superblockInfo.SuperblockDeltaQ);
+
+ for (Av1Plane plane = 0; (int)plane < Av1Constants.MaxPlanes; plane++)
+ {
+ int dcDeltaQ = this.frameHeader.QuantizationParameters.DeltaQDc[(int)plane];
+ int acDeltaQ = this.frameHeader.QuantizationParameters.DeltaQAc[(int)plane];
+
+ this.deQuantsDeltaQ.SetDc(i, plane, Av1QuantizationLookup.GetDcQuant(currentQIndex, dcDeltaQ, bitDepth));
+ this.deQuantsDeltaQ.SetAc(i, plane, Av1QuantizationLookup.GetAcQuant(currentQIndex, acDeltaQ, bitDepth));
+ }
+ }
+ }
+ }
+
+ public int InverseQuantize(Av1BlockModeInfo mode, Span level, Span qCoefficients, Av1TransformType transformType, Av1TransformSize transformSize, Av1Plane plane)
+ {
+ Guard.NotNull(this.deQuantsDeltaQ);
+ Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType);
+ short[] scanIndices = scanOrder.Scan;
+ int maxValue = (1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount())) - 1;
+ int minValue = -(1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount()));
+ Av1TransformSize qmTransformSize = transformSize.GetAdjusted();
+ bool usingQuantizationMatrix = this.frameHeader.QuantizationParameters.IsUsingQMatrix;
+ bool lossless = this.frameHeader.LosslessArray[mode.SegmentId];
+ short dequantDc = this.deQuantsDeltaQ.GetDc(mode.SegmentId, plane);
+ short dequantAc = this.deQuantsDeltaQ.GetAc(mode.SegmentId, plane);
+ int qmLevel = lossless || !usingQuantizationMatrix ? Av1ScanOrderConstants.QuantizationMatrixLevelCount - 1 : this.frameHeader.QuantizationParameters.QMatrix[(int)plane];
+ ref int iqMatrix = ref (transformType.ToClass() == Av1TransformClass.Class2D) ?
+ ref this.inverseQuantizationMatrix[qmLevel][(int)plane][(int)qmTransformSize]
+ : ref this.inverseQuantizationMatrix[Av1Constants.QuantificationMatrixLevelCount - 1][0][(int)qmTransformSize];
+ int shift = transformSize.GetScale();
+
+ int coefficientCount = level[0];
+ level = level[1..];
+ int lev = level[0];
+ int qCoefficient;
+ if (lev != 0)
+ {
+ int pos = scanIndices[0];
+ qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantDc, pos, ref iqMatrix)) & 0xffffff);
+ qCoefficient >>= shift;
+
+ if (lev < 0)
+ {
+ qCoefficient = -qCoefficient;
+ }
+
+ qCoefficients[0] = Av1Math.Clamp(qCoefficient, minValue, maxValue);
+ }
+
+ for (int i = 1; i < coefficientCount; i++)
+ {
+ lev = level[i];
+ if (lev != 0)
+ {
+ int pos = scanIndices[i];
+ qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantAc, pos, ref iqMatrix)) & 0xffffff);
+ qCoefficient >>= shift;
+
+ if (lev < 0)
+ {
+ qCoefficient = -qCoefficient;
+ }
+
+ qCoefficients[pos] = Av1Math.Clamp(qCoefficient, minValue, maxValue);
+ }
+ }
+
+ return coefficientCount;
+ }
+
+ private static int GetDeQuantizedValue(short dequant, int coefficientIndex, ref int iqMatrix)
+ {
+ int deQuantifiedValue = dequant;
+
+ // TODO: Check order of operators
+ deQuantifiedValue = ((Unsafe.Add(ref iqMatrix, coefficientIndex) * deQuantifiedValue) + (1 << (Av1ScanOrderConstants.QuantizationMatrixLevelBitCount - 1))) >> Av1ScanOrderConstants.QuantizationMatrixLevelBitCount;
+ return deQuantifiedValue;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationConstants.cs
new file mode 100644
index 0000000000..ea9fb72415
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationConstants.cs
@@ -0,0 +1,483 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
+
+internal class Av1QuantizationConstants
+{
+ public static int[][][] InverseWT =>
+ [
+ [
+
+ // Luma
+ [
+
+ // Size 4x4
+ 32, 43, 73, 97, 43, 67, 94, 110, 73, 94, 137, 150, 97, 110, 150, 200,
+
+ // Size 8x8
+ 32, 32, 38, 51, 68, 84, 95, 109, 32, 35, 40, 49, 63, 76, 89, 102, 38,
+ 40, 54, 65, 78, 91, 98, 106, 51, 49, 65, 82, 97, 111, 113, 121, 68, 63,
+ 78, 97, 117, 134, 138, 142, 84, 76, 91, 111, 134, 152, 159, 168, 95, 89,
+ 98, 113, 138, 159, 183, 199, 109, 102, 106, 121, 142, 168, 199, 220,
+
+ // Size 16x16
+ 32, 31, 31, 34, 36, 44, 48, 59, 65, 80, 83, 91, 97, 104, 111, 119, 31,
+ 32, 32, 33, 34, 41, 44, 54, 59, 72, 75, 83, 90, 97, 104, 112, 31, 32,
+ 33, 35, 36, 42, 45, 54, 59, 71, 74, 81, 86, 93, 100, 107, 34, 33, 35,
+ 39, 42, 47, 51, 58, 63, 74, 76, 81, 84, 90, 97, 105, 36, 34, 36, 42, 48,
+ 54, 57, 64, 68, 79, 81, 88, 91, 96, 102, 105, 44, 41, 42, 47, 54, 63,
+ 67, 75, 79, 90, 92, 95, 100, 102, 109, 112, 48, 44, 45, 51, 57, 67, 71,
+ 80, 85, 96, 99, 107, 108, 111, 117, 120, 59, 54, 54, 58, 64, 75, 80, 92,
+ 98, 110, 113, 115, 116, 122, 125, 130, 65, 59, 59, 63, 68, 79, 85, 98,
+ 105, 118, 121, 127, 130, 134, 135, 140, 80, 72, 71, 74, 79, 90, 96, 110,
+ 118, 134, 137, 140, 143, 144, 146, 152, 83, 75, 74, 76, 81, 92, 99, 113,
+ 121, 137, 140, 151, 152, 155, 158, 165, 91, 83, 81, 81, 88, 95, 107,
+ 115, 127, 140, 151, 159, 166, 169, 173, 179, 97, 90, 86, 84, 91, 100,
+ 108, 116, 130, 143, 152, 166, 174, 182, 189, 193, 104, 97, 93, 90, 96,
+ 102, 111, 122, 134, 144, 155, 169, 182, 191, 200, 210, 111, 104, 100,
+ 97, 102, 109, 117, 125, 135, 146, 158, 173, 189, 200, 210, 220, 119,
+ 112, 107, 105, 105, 112, 120, 130, 140, 152, 165, 179, 193, 210, 220,
+ 231,
+
+ // Size 32x32
+ 32, 31, 31, 31, 31, 32, 34, 35, 36, 39, 44, 46, 48, 54, 59, 62, 65, 71,
+ 80, 81, 83, 88, 91, 94, 97, 101, 104, 107, 111, 115, 119, 123, 31, 32,
+ 32, 32, 32, 32, 34, 34, 35, 38, 42, 44, 46, 51, 56, 59, 62, 68, 76, 77,
+ 78, 84, 86, 89, 92, 95, 99, 102, 105, 109, 113, 116, 31, 32, 32, 32, 32,
+ 32, 33, 34, 34, 37, 41, 42, 44, 49, 54, 56, 59, 65, 72, 73, 75, 80, 83,
+ 86, 90, 93, 97, 101, 104, 108, 112, 116, 31, 32, 32, 32, 33, 33, 34, 35,
+ 35, 38, 41, 43, 45, 49, 54, 56, 59, 64, 72, 73, 74, 79, 82, 85, 88, 91,
+ 94, 97, 101, 104, 107, 111, 31, 32, 32, 33, 33, 34, 35, 36, 36, 39, 42,
+ 44, 45, 50, 54, 56, 59, 64, 71, 72, 74, 78, 81, 84, 86, 89, 93, 96, 100,
+ 104, 107, 111, 32, 32, 32, 33, 34, 35, 37, 37, 38, 40, 42, 44, 46, 49,
+ 53, 55, 58, 63, 69, 70, 72, 76, 79, 82, 85, 89, 93, 96, 99, 102, 106,
+ 109, 34, 34, 33, 34, 35, 37, 39, 41, 42, 45, 47, 49, 51, 54, 58, 60, 63,
+ 68, 74, 75, 76, 80, 81, 82, 84, 87, 90, 93, 97, 101, 105, 110, 35, 34,
+ 34, 35, 36, 37, 41, 43, 45, 47, 50, 52, 53, 57, 61, 63, 65, 70, 76, 77,
+ 79, 82, 84, 86, 89, 91, 92, 93, 96, 100, 103, 107, 36, 35, 34, 35, 36,
+ 38, 42, 45, 48, 50, 54, 55, 57, 60, 64, 66, 68, 73, 79, 80, 81, 85, 88,
+ 90, 91, 93, 96, 99, 102, 103, 105, 107, 39, 38, 37, 38, 39, 40, 45, 47,
+ 50, 54, 58, 59, 61, 65, 69, 71, 73, 78, 84, 85, 86, 91, 92, 92, 95, 98,
+ 100, 101, 103, 106, 110, 114, 44, 42, 41, 41, 42, 42, 47, 50, 54, 58,
+ 63, 65, 67, 71, 75, 77, 79, 84, 90, 91, 92, 95, 95, 97, 100, 101, 102,
+ 105, 109, 111, 112, 114, 46, 44, 42, 43, 44, 44, 49, 52, 55, 59, 65, 67,
+ 69, 74, 78, 80, 82, 87, 93, 94, 95, 98, 100, 103, 102, 105, 108, 110,
+ 111, 113, 117, 121, 48, 46, 44, 45, 45, 46, 51, 53, 57, 61, 67, 69, 71,
+ 76, 80, 83, 85, 90, 96, 97, 99, 103, 107, 105, 108, 111, 111, 113, 117,
+ 119, 120, 122, 54, 51, 49, 49, 50, 49, 54, 57, 60, 65, 71, 74, 76, 82,
+ 87, 89, 92, 97, 104, 105, 106, 111, 110, 111, 114, 113, 116, 120, 120,
+ 121, 125, 130, 59, 56, 54, 54, 54, 53, 58, 61, 64, 69, 75, 78, 80, 87,
+ 92, 95, 98, 103, 110, 111, 113, 115, 115, 119, 116, 120, 122, 122, 125,
+ 129, 130, 130, 62, 59, 56, 56, 56, 55, 60, 63, 66, 71, 77, 80, 83, 89,
+ 95, 98, 101, 107, 114, 115, 117, 119, 123, 121, 125, 126, 125, 129, 131,
+ 131, 135, 140, 65, 62, 59, 59, 59, 58, 63, 65, 68, 73, 79, 82, 85, 92,
+ 98, 101, 105, 111, 118, 119, 121, 126, 127, 128, 130, 130, 134, 133,
+ 135, 140, 140, 140, 71, 68, 65, 64, 64, 63, 68, 70, 73, 78, 84, 87, 90,
+ 97, 103, 107, 111, 117, 125, 126, 128, 134, 132, 136, 133, 138, 137,
+ 140, 143, 142, 145, 150, 80, 76, 72, 72, 71, 69, 74, 76, 79, 84, 90, 93,
+ 96, 104, 110, 114, 118, 125, 134, 135, 137, 139, 140, 139, 143, 142,
+ 144, 146, 146, 151, 152, 151, 81, 77, 73, 73, 72, 70, 75, 77, 80, 85,
+ 91, 94, 97, 105, 111, 115, 119, 126, 135, 137, 138, 144, 147, 146, 148,
+ 149, 151, 150, 156, 155, 157, 163, 83, 78, 75, 74, 74, 72, 76, 79, 81,
+ 86, 92, 95, 99, 106, 113, 117, 121, 128, 137, 138, 140, 147, 151, 156,
+ 152, 157, 155, 161, 158, 162, 165, 164, 88, 84, 80, 79, 78, 76, 80, 82,
+ 85, 91, 95, 98, 103, 111, 115, 119, 126, 134, 139, 144, 147, 152, 154,
+ 158, 163, 159, 165, 163, 168, 168, 169, 176, 91, 86, 83, 82, 81, 79, 81,
+ 84, 88, 92, 95, 100, 107, 110, 115, 123, 127, 132, 140, 147, 151, 154,
+ 159, 161, 166, 171, 169, 173, 173, 176, 179, 177, 94, 89, 86, 85, 84,
+ 82, 82, 86, 90, 92, 97, 103, 105, 111, 119, 121, 128, 136, 139, 146,
+ 156, 158, 161, 166, 168, 174, 179, 178, 180, 183, 183, 190, 97, 92, 90,
+ 88, 86, 85, 84, 89, 91, 95, 100, 102, 108, 114, 116, 125, 130, 133, 143,
+ 148, 152, 163, 166, 168, 174, 176, 182, 187, 189, 188, 193, 191, 101,
+ 95, 93, 91, 89, 89, 87, 91, 93, 98, 101, 105, 111, 113, 120, 126, 130,
+ 138, 142, 149, 157, 159, 171, 174, 176, 183, 184, 191, 195, 199, 197,
+ 204, 104, 99, 97, 94, 93, 93, 90, 92, 96, 100, 102, 108, 111, 116, 122,
+ 125, 134, 137, 144, 151, 155, 165, 169, 179, 182, 184, 191, 193, 200,
+ 204, 210, 206, 107, 102, 101, 97, 96, 96, 93, 93, 99, 101, 105, 110,
+ 113, 120, 122, 129, 133, 140, 146, 150, 161, 163, 173, 178, 187, 191,
+ 193, 200, 202, 210, 214, 222, 111, 105, 104, 101, 100, 99, 97, 96, 102,
+ 103, 109, 111, 117, 120, 125, 131, 135, 143, 146, 156, 158, 168, 173,
+ 180, 189, 195, 200, 202, 210, 212, 220, 224, 115, 109, 108, 104, 104,
+ 102, 101, 100, 103, 106, 111, 113, 119, 121, 129, 131, 140, 142, 151,
+ 155, 162, 168, 176, 183, 188, 199, 204, 210, 212, 220, 222, 230, 119,
+ 113, 112, 107, 107, 106, 105, 103, 105, 110, 112, 117, 120, 125, 130,
+ 135, 140, 145, 152, 157, 165, 169, 179, 183, 193, 197, 210, 214, 220,
+ 222, 231, 232, 123, 116, 116, 111, 111, 109, 110, 107, 107, 114, 114,
+ 121, 122, 130, 130, 140, 140, 150, 151, 163, 164, 176, 177, 190, 191,
+ 204, 206, 222, 224, 230, 232, 242,
+
+ // Size 4x8
+ 32, 42, 75, 91, 33, 42, 69, 86, 37, 58, 84, 91, 49, 71, 103, 110, 65,
+ 84, 125, 128, 80, 97, 142, 152, 91, 100, 145, 178, 104, 112, 146, 190,
+
+ // Size 8x4
+ 32, 33, 37, 49, 65, 80, 91, 104, 42, 42, 58, 71, 84, 97, 100, 112, 75,
+ 69, 84, 103, 125, 142, 145, 146, 91, 86, 91, 110, 128, 152, 178, 190,
+
+ // Size 8x16
+ 32, 32, 36, 53, 65, 87, 93, 99, 31, 33, 34, 49, 59, 78, 86, 93, 32, 34,
+ 36, 50, 59, 77, 82, 89, 34, 37, 42, 54, 63, 79, 80, 88, 36, 38, 48, 60,
+ 68, 84, 86, 90, 44, 43, 53, 71, 79, 95, 94, 97, 48, 46, 56, 76, 85, 102,
+ 105, 105, 58, 54, 63, 87, 98, 116, 112, 115, 65, 58, 68, 92, 105, 124,
+ 122, 124, 79, 70, 79, 104, 118, 141, 135, 135, 82, 72, 81, 106, 121,
+ 144, 149, 146, 91, 80, 88, 106, 130, 148, 162, 159, 97, 86, 94, 107,
+ 128, 157, 167, 171, 103, 93, 98, 114, 131, 150, 174, 186, 110, 100, 101,
+ 117, 138, 161, 183, 193, 118, 107, 105, 118, 136, 157, 182, 203,
+
+ // Size 16x8
+ 32, 31, 32, 34, 36, 44, 48, 58, 65, 79, 82, 91, 97, 103, 110, 118, 32,
+ 33, 34, 37, 38, 43, 46, 54, 58, 70, 72, 80, 86, 93, 100, 107, 36, 34,
+ 36, 42, 48, 53, 56, 63, 68, 79, 81, 88, 94, 98, 101, 105, 53, 49, 50,
+ 54, 60, 71, 76, 87, 92, 104, 106, 106, 107, 114, 117, 118, 65, 59, 59,
+ 63, 68, 79, 85, 98, 105, 118, 121, 130, 128, 131, 138, 136, 87, 78, 77,
+ 79, 84, 95, 102, 116, 124, 141, 144, 148, 157, 150, 161, 157, 93, 86,
+ 82, 80, 86, 94, 105, 112, 122, 135, 149, 162, 167, 174, 183, 182, 99,
+ 93, 89, 88, 90, 97, 105, 115, 124, 135, 146, 159, 171, 186, 193, 203,
+
+ // Size 16x32
+ 32, 31, 32, 34, 36, 44, 53, 59, 65, 79, 87, 90, 93, 96, 99, 102, 31, 32,
+ 32, 34, 35, 42, 51, 56, 62, 75, 82, 85, 88, 91, 94, 97, 31, 32, 33, 33,
+ 34, 41, 49, 54, 59, 72, 78, 82, 86, 90, 93, 97, 31, 32, 33, 34, 35, 41,
+ 49, 54, 59, 71, 78, 81, 84, 87, 90, 93, 32, 32, 34, 35, 36, 42, 50, 54,
+ 59, 71, 77, 80, 82, 86, 89, 93, 32, 33, 35, 37, 38, 42, 49, 53, 58, 69,
+ 75, 78, 82, 86, 89, 92, 34, 34, 37, 39, 42, 48, 54, 58, 63, 73, 79, 78,
+ 80, 83, 88, 92, 35, 34, 37, 41, 45, 50, 57, 61, 65, 76, 82, 83, 84, 84,
+ 87, 90, 36, 34, 38, 43, 48, 54, 60, 64, 68, 78, 84, 87, 86, 89, 90, 90,
+ 39, 37, 40, 45, 50, 58, 65, 69, 73, 84, 89, 89, 91, 91, 93, 96, 44, 41,
+ 43, 48, 53, 63, 71, 75, 79, 90, 95, 93, 94, 95, 97, 97, 46, 43, 44, 49,
+ 55, 65, 73, 78, 82, 93, 98, 100, 98, 100, 99, 103, 48, 45, 46, 51, 56,
+ 67, 76, 80, 85, 96, 102, 102, 105, 102, 105, 104, 53, 49, 50, 54, 60,
+ 71, 82, 87, 92, 103, 109, 107, 107, 110, 107, 111, 58, 54, 54, 58, 63,
+ 75, 87, 92, 98, 110, 116, 115, 112, 111, 115, 112, 61, 57, 56, 60, 66,
+ 77, 89, 95, 101, 114, 120, 118, 119, 118, 116, 120, 65, 60, 58, 63, 68,
+ 79, 92, 98, 105, 118, 124, 123, 122, 123, 124, 121, 71, 65, 63, 68, 73,
+ 84, 97, 103, 111, 125, 132, 132, 130, 128, 127, 130, 79, 72, 70, 74, 79,
+ 90, 104, 110, 118, 133, 141, 136, 135, 135, 135, 131, 81, 74, 71, 75,
+ 80, 91, 105, 112, 119, 135, 142, 140, 140, 138, 139, 142, 82, 75, 72,
+ 76, 81, 92, 106, 113, 121, 136, 144, 151, 149, 149, 146, 143, 88, 80,
+ 77, 80, 85, 97, 108, 115, 126, 142, 149, 153, 153, 152, 152, 154, 91,
+ 83, 80, 81, 88, 100, 106, 114, 130, 142, 148, 155, 162, 160, 159, 155,
+ 94, 85, 83, 82, 91, 100, 105, 118, 131, 137, 153, 160, 165, 167, 166,
+ 168, 97, 88, 86, 85, 94, 100, 107, 123, 128, 140, 157, 161, 167, 173,
+ 171, 169, 100, 91, 89, 87, 97, 100, 111, 121, 127, 145, 152, 164, 173,
+ 178, 182, 181, 103, 94, 93, 90, 98, 101, 114, 120, 131, 144, 150, 170,
+ 174, 180, 186, 183, 107, 97, 96, 93, 100, 104, 117, 119, 136, 142, 155,
+ 168, 177, 187, 191, 198, 110, 101, 100, 97, 101, 108, 117, 123, 138,
+ 141, 161, 165, 183, 188, 193, 200, 114, 104, 104, 100, 103, 112, 117,
+ 127, 137, 146, 159, 167, 185, 190, 201, 206, 118, 108, 107, 103, 105,
+ 115, 118, 131, 136, 151, 157, 172, 182, 197, 203, 208, 122, 111, 111,
+ 107, 107, 119, 119, 136, 136, 156, 156, 178, 179, 203, 204, 217,
+
+ // Size 32x16
+ 32, 31, 31, 31, 32, 32, 34, 35, 36, 39, 44, 46, 48, 53, 58, 61, 65, 71,
+ 79, 81, 82, 88, 91, 94, 97, 100, 103, 107, 110, 114, 118, 122, 31, 32,
+ 32, 32, 32, 33, 34, 34, 34, 37, 41, 43, 45, 49, 54, 57, 60, 65, 72, 74,
+ 75, 80, 83, 85, 88, 91, 94, 97, 101, 104, 108, 111, 32, 32, 33, 33, 34,
+ 35, 37, 37, 38, 40, 43, 44, 46, 50, 54, 56, 58, 63, 70, 71, 72, 77, 80,
+ 83, 86, 89, 93, 96, 100, 104, 107, 111, 34, 34, 33, 34, 35, 37, 39, 41,
+ 43, 45, 48, 49, 51, 54, 58, 60, 63, 68, 74, 75, 76, 80, 81, 82, 85, 87,
+ 90, 93, 97, 100, 103, 107, 36, 35, 34, 35, 36, 38, 42, 45, 48, 50, 53,
+ 55, 56, 60, 63, 66, 68, 73, 79, 80, 81, 85, 88, 91, 94, 97, 98, 100,
+ 101, 103, 105, 107, 44, 42, 41, 41, 42, 42, 48, 50, 54, 58, 63, 65, 67,
+ 71, 75, 77, 79, 84, 90, 91, 92, 97, 100, 100, 100, 100, 101, 104, 108,
+ 112, 115, 119, 53, 51, 49, 49, 50, 49, 54, 57, 60, 65, 71, 73, 76, 82,
+ 87, 89, 92, 97, 104, 105, 106, 108, 106, 105, 107, 111, 114, 117, 117,
+ 117, 118, 119, 59, 56, 54, 54, 54, 53, 58, 61, 64, 69, 75, 78, 80, 87,
+ 92, 95, 98, 103, 110, 112, 113, 115, 114, 118, 123, 121, 120, 119, 123,
+ 127, 131, 136, 65, 62, 59, 59, 59, 58, 63, 65, 68, 73, 79, 82, 85, 92,
+ 98, 101, 105, 111, 118, 119, 121, 126, 130, 131, 128, 127, 131, 136,
+ 138, 137, 136, 136, 79, 75, 72, 71, 71, 69, 73, 76, 78, 84, 90, 93, 96,
+ 103, 110, 114, 118, 125, 133, 135, 136, 142, 142, 137, 140, 145, 144,
+ 142, 141, 146, 151, 156, 87, 82, 78, 78, 77, 75, 79, 82, 84, 89, 95, 98,
+ 102, 109, 116, 120, 124, 132, 141, 142, 144, 149, 148, 153, 157, 152,
+ 150, 155, 161, 159, 157, 156, 90, 85, 82, 81, 80, 78, 78, 83, 87, 89,
+ 93, 100, 102, 107, 115, 118, 123, 132, 136, 140, 151, 153, 155, 160,
+ 161, 164, 170, 168, 165, 167, 172, 178, 93, 88, 86, 84, 82, 82, 80, 84,
+ 86, 91, 94, 98, 105, 107, 112, 119, 122, 130, 135, 140, 149, 153, 162,
+ 165, 167, 173, 174, 177, 183, 185, 182, 179, 96, 91, 90, 87, 86, 86, 83,
+ 84, 89, 91, 95, 100, 102, 110, 111, 118, 123, 128, 135, 138, 149, 152,
+ 160, 167, 173, 178, 180, 187, 188, 190, 197, 203, 99, 94, 93, 90, 89,
+ 89, 88, 87, 90, 93, 97, 99, 105, 107, 115, 116, 124, 127, 135, 139, 146,
+ 152, 159, 166, 171, 182, 186, 191, 193, 201, 203, 204, 102, 97, 97, 93,
+ 93, 92, 92, 90, 90, 96, 97, 103, 104, 111, 112, 120, 121, 130, 131, 142,
+ 143, 154, 155, 168, 169, 181, 183, 198, 200, 206, 208, 217,
+
+ // Size 4x16
+ 31, 44, 79, 96, 32, 41, 72, 90, 32, 42, 71, 86, 34, 48, 73, 83, 34, 54,
+ 78, 89, 41, 63, 90, 95, 45, 67, 96, 102, 54, 75, 110, 111, 60, 79, 118,
+ 123, 72, 90, 133, 135, 75, 92, 136, 149, 83, 100, 142, 160, 88, 100,
+ 140, 173, 94, 101, 144, 180, 101, 108, 141, 188, 108, 115, 151, 197,
+
+ // Size 16x4
+ 31, 32, 32, 34, 34, 41, 45, 54, 60, 72, 75, 83, 88, 94, 101, 108, 44,
+ 41, 42, 48, 54, 63, 67, 75, 79, 90, 92, 100, 100, 101, 108, 115, 79, 72,
+ 71, 73, 78, 90, 96, 110, 118, 133, 136, 142, 140, 144, 141, 151, 96, 90,
+ 86, 83, 89, 95, 102, 111, 123, 135, 149, 160, 173, 180, 188, 197,
+
+ // Size 8x32
+ 32, 32, 36, 53, 65, 87, 93, 99, 31, 32, 35, 51, 62, 82, 88, 94, 31, 33,
+ 34, 49, 59, 78, 86, 93, 31, 33, 35, 49, 59, 78, 84, 90, 32, 34, 36, 50,
+ 59, 77, 82, 89, 32, 35, 38, 49, 58, 75, 82, 89, 34, 37, 42, 54, 63, 79,
+ 80, 88, 35, 37, 45, 57, 65, 82, 84, 87, 36, 38, 48, 60, 68, 84, 86, 90,
+ 39, 40, 50, 65, 73, 89, 91, 93, 44, 43, 53, 71, 79, 95, 94, 97, 46, 44,
+ 55, 73, 82, 98, 98, 99, 48, 46, 56, 76, 85, 102, 105, 105, 53, 50, 60,
+ 82, 92, 109, 107, 107, 58, 54, 63, 87, 98, 116, 112, 115, 61, 56, 66,
+ 89, 101, 120, 119, 116, 65, 58, 68, 92, 105, 124, 122, 124, 71, 63, 73,
+ 97, 111, 132, 130, 127, 79, 70, 79, 104, 118, 141, 135, 135, 81, 71, 80,
+ 105, 119, 142, 140, 139, 82, 72, 81, 106, 121, 144, 149, 146, 88, 77,
+ 85, 108, 126, 149, 153, 152, 91, 80, 88, 106, 130, 148, 162, 159, 94,
+ 83, 91, 105, 131, 153, 165, 166, 97, 86, 94, 107, 128, 157, 167, 171,
+ 100, 89, 97, 111, 127, 152, 173, 182, 103, 93, 98, 114, 131, 150, 174,
+ 186, 107, 96, 100, 117, 136, 155, 177, 191, 110, 100, 101, 117, 138,
+ 161, 183, 193, 114, 104, 103, 117, 137, 159, 185, 201, 118, 107, 105,
+ 118, 136, 157, 182, 203, 122, 111, 107, 119, 136, 156, 179, 204,
+
+ // Size 32x8
+ 32, 31, 31, 31, 32, 32, 34, 35, 36, 39, 44, 46, 48, 53, 58, 61, 65, 71,
+ 79, 81, 82, 88, 91, 94, 97, 100, 103, 107, 110, 114, 118, 122, 32, 32,
+ 33, 33, 34, 35, 37, 37, 38, 40, 43, 44, 46, 50, 54, 56, 58, 63, 70, 71,
+ 72, 77, 80, 83, 86, 89, 93, 96, 100, 104, 107, 111, 36, 35, 34, 35, 36,
+ 38, 42, 45, 48, 50, 53, 55, 56, 60, 63, 66, 68, 73, 79, 80, 81, 85, 88,
+ 91, 94, 97, 98, 100, 101, 103, 105, 107, 53, 51, 49, 49, 50, 49, 54, 57,
+ 60, 65, 71, 73, 76, 82, 87, 89, 92, 97, 104, 105, 106, 108, 106, 105,
+ 107, 111, 114, 117, 117, 117, 118, 119, 65, 62, 59, 59, 59, 58, 63, 65,
+ 68, 73, 79, 82, 85, 92, 98, 101, 105, 111, 118, 119, 121, 126, 130, 131,
+ 128, 127, 131, 136, 138, 137, 136, 136, 87, 82, 78, 78, 77, 75, 79, 82,
+ 84, 89, 95, 98, 102, 109, 116, 120, 124, 132, 141, 142, 144, 149, 148,
+ 153, 157, 152, 150, 155, 161, 159, 157, 156, 93, 88, 86, 84, 82, 82, 80,
+ 84, 86, 91, 94, 98, 105, 107, 112, 119, 122, 130, 135, 140, 149, 153,
+ 162, 165, 167, 173, 174, 177, 183, 185, 182, 179, 99, 94, 93, 90, 89,
+ 89, 88, 87, 90, 93, 97, 99, 105, 107, 115, 116, 124, 127, 135, 139, 146,
+ 152, 159, 166, 171, 182, 186, 191, 193, 201, 203, 204
+ ],
+
+ // Chroma
+ [
+
+ // Size 4x4
+ 35, 46, 57, 66, 46, 60, 69, 71, 57, 69, 90, 90, 66, 71, 90, 109,
+
+ // Size 8x8
+ 31, 38, 47, 50, 57, 63, 67, 71, 38, 47, 46, 47, 52, 57, 62, 67, 47, 46,
+ 54, 57, 61, 66, 67, 68, 50, 47, 57, 66, 72, 77, 75, 75, 57, 52, 61, 72,
+ 82, 88, 86, 84, 63, 57, 66, 77, 88, 96, 95, 95, 67, 62, 67, 75, 86, 95,
+ 104, 107, 71, 67, 68, 75, 84, 95, 107, 113,
+
+ // Size 16x16
+ 32, 30, 33, 41, 49, 49, 50, 54, 57, 63, 65, 68, 70, 72, 74, 76, 30, 32,
+ 35, 42, 46, 45, 46, 49, 52, 57, 58, 62, 64, 67, 70, 72, 33, 35, 39, 45,
+ 47, 45, 46, 49, 51, 56, 57, 60, 62, 64, 66, 69, 41, 42, 45, 48, 50, 49,
+ 50, 52, 53, 57, 58, 59, 60, 61, 64, 67, 49, 46, 47, 50, 53, 53, 54, 55,
+ 56, 60, 61, 64, 64, 65, 66, 66, 49, 45, 45, 49, 53, 58, 60, 62, 63, 67,
+ 68, 67, 69, 68, 70, 70, 50, 46, 46, 50, 54, 60, 61, 65, 67, 71, 71, 74,
+ 73, 73, 74, 74, 54, 49, 49, 52, 55, 62, 65, 71, 73, 78, 79, 78, 77, 78,
+ 78, 78, 57, 52, 51, 53, 56, 63, 67, 73, 76, 82, 83, 84, 84, 84, 82, 83,
+ 63, 57, 56, 57, 60, 67, 71, 78, 82, 89, 90, 90, 89, 88, 87, 88, 65, 58,
+ 57, 58, 61, 68, 71, 79, 83, 90, 91, 94, 93, 93, 92, 93, 68, 62, 60, 59,
+ 64, 67, 74, 78, 84, 90, 94, 98, 99, 98, 98, 98, 70, 64, 62, 60, 64, 69,
+ 73, 77, 84, 89, 93, 99, 102, 103, 104, 104, 72, 67, 64, 61, 65, 68, 73,
+ 78, 84, 88, 93, 98, 103, 106, 108, 109, 74, 70, 66, 64, 66, 70, 74, 78,
+ 82, 87, 92, 98, 104, 108, 111, 112, 76, 72, 69, 67, 66, 70, 74, 78, 83,
+ 88, 93, 98, 104, 109, 112, 116,
+
+ // Size 32x32
+ 32, 31, 30, 32, 33, 36, 41, 45, 49, 48, 49, 50, 50, 52, 54, 56, 57, 60,
+ 63, 64, 65, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 78, 31, 31, 31, 33,
+ 34, 38, 42, 45, 47, 47, 47, 47, 48, 50, 52, 53, 54, 57, 60, 61, 61, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 30, 31, 32, 33, 35, 40, 42, 44,
+ 46, 45, 45, 45, 46, 47, 49, 51, 52, 54, 57, 58, 58, 61, 62, 63, 64, 66,
+ 67, 68, 70, 71, 72, 74, 32, 33, 33, 35, 37, 41, 43, 45, 47, 46, 45, 46,
+ 46, 47, 49, 50, 51, 54, 57, 57, 58, 60, 61, 62, 63, 64, 65, 66, 67, 68,
+ 69, 70, 33, 34, 35, 37, 39, 43, 45, 46, 47, 46, 45, 46, 46, 47, 49, 50,
+ 51, 53, 56, 57, 57, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 36, 38,
+ 40, 41, 43, 47, 47, 47, 48, 46, 45, 46, 46, 47, 48, 49, 50, 52, 54, 55,
+ 55, 57, 58, 59, 61, 62, 64, 65, 66, 67, 68, 69, 41, 42, 42, 43, 45, 47,
+ 48, 49, 50, 49, 49, 49, 50, 50, 52, 52, 53, 55, 57, 58, 58, 60, 59, 59,
+ 60, 61, 61, 63, 64, 66, 67, 69, 45, 45, 44, 45, 46, 47, 49, 50, 51, 51,
+ 51, 51, 52, 52, 53, 54, 55, 57, 59, 59, 60, 61, 61, 62, 63, 63, 63, 63,
+ 63, 64, 65, 66, 49, 47, 46, 47, 47, 48, 50, 51, 53, 53, 53, 54, 54, 54,
+ 55, 56, 56, 58, 60, 61, 61, 63, 64, 64, 64, 64, 65, 66, 66, 66, 66, 66,
+ 48, 47, 45, 46, 46, 46, 49, 51, 53, 54, 55, 56, 56, 57, 58, 59, 60, 61,
+ 63, 64, 64, 66, 66, 65, 66, 67, 67, 67, 67, 68, 69, 70, 49, 47, 45, 45,
+ 45, 45, 49, 51, 53, 55, 58, 59, 60, 61, 62, 63, 63, 65, 67, 67, 68, 69,
+ 67, 68, 69, 68, 68, 69, 70, 70, 70, 70, 50, 47, 45, 46, 46, 46, 49, 51,
+ 54, 56, 59, 60, 60, 62, 64, 64, 65, 67, 69, 69, 70, 70, 71, 71, 70, 70,
+ 71, 71, 71, 71, 72, 74, 50, 48, 46, 46, 46, 46, 50, 52, 54, 56, 60, 60,
+ 61, 63, 65, 66, 67, 68, 71, 71, 71, 73, 74, 72, 73, 74, 73, 73, 74, 74,
+ 74, 74, 52, 50, 47, 47, 47, 47, 50, 52, 54, 57, 61, 62, 63, 66, 68, 69,
+ 70, 72, 75, 75, 75, 77, 75, 75, 76, 75, 75, 76, 75, 75, 76, 77, 54, 52,
+ 49, 49, 49, 48, 52, 53, 55, 58, 62, 64, 65, 68, 71, 72, 73, 75, 78, 78,
+ 79, 79, 78, 79, 77, 78, 78, 77, 78, 79, 78, 78, 56, 53, 51, 50, 50, 49,
+ 52, 54, 56, 59, 63, 64, 66, 69, 72, 73, 75, 77, 80, 80, 81, 81, 82, 80,
+ 81, 81, 79, 81, 80, 79, 81, 82, 57, 54, 52, 51, 51, 50, 53, 55, 56, 60,
+ 63, 65, 67, 70, 73, 75, 76, 79, 82, 82, 83, 85, 84, 83, 84, 83, 84, 82,
+ 82, 84, 83, 82, 60, 57, 54, 54, 53, 52, 55, 57, 58, 61, 65, 67, 68, 72,
+ 75, 77, 79, 82, 85, 85, 86, 88, 86, 87, 85, 86, 85, 85, 86, 84, 85, 86,
+ 63, 60, 57, 57, 56, 54, 57, 59, 60, 63, 67, 69, 71, 75, 78, 80, 82, 85,
+ 89, 89, 90, 90, 90, 89, 89, 88, 88, 88, 87, 88, 88, 87, 64, 61, 58, 57,
+ 57, 55, 58, 59, 61, 64, 67, 69, 71, 75, 78, 80, 82, 85, 89, 90, 91, 92,
+ 93, 92, 92, 91, 91, 90, 91, 90, 90, 92, 65, 61, 58, 58, 57, 55, 58, 60,
+ 61, 64, 68, 70, 71, 75, 79, 81, 83, 86, 90, 91, 91, 94, 94, 96, 93, 94,
+ 93, 94, 92, 93, 93, 92, 67, 63, 61, 60, 59, 57, 60, 61, 63, 66, 69, 70,
+ 73, 77, 79, 81, 85, 88, 90, 92, 94, 96, 96, 97, 98, 95, 97, 95, 96, 95,
+ 95, 96, 68, 64, 62, 61, 60, 58, 59, 61, 64, 66, 67, 71, 74, 75, 78, 82,
+ 84, 86, 90, 93, 94, 96, 98, 98, 99, 100, 98, 99, 98, 98, 98, 97, 69, 65,
+ 63, 62, 61, 59, 59, 62, 64, 65, 68, 71, 72, 75, 79, 80, 83, 87, 89, 92,
+ 96, 97, 98, 100, 100, 101, 102, 101, 101, 101, 100, 102, 70, 66, 64, 63,
+ 62, 61, 60, 63, 64, 66, 69, 70, 73, 76, 77, 81, 84, 85, 89, 92, 93, 98,
+ 99, 100, 102, 102, 103, 104, 104, 103, 104, 102, 71, 67, 66, 64, 63, 62,
+ 61, 63, 64, 67, 68, 70, 74, 75, 78, 81, 83, 86, 88, 91, 94, 95, 100,
+ 101, 102, 104, 104, 105, 106, 107, 105, 107, 72, 68, 67, 65, 64, 64, 61,
+ 63, 65, 67, 68, 71, 73, 75, 78, 79, 84, 85, 88, 91, 93, 97, 98, 102,
+ 103, 104, 106, 106, 108, 108, 109, 107, 73, 69, 68, 66, 65, 65, 63, 63,
+ 66, 67, 69, 71, 73, 76, 77, 81, 82, 85, 88, 90, 94, 95, 99, 101, 104,
+ 105, 106, 109, 108, 110, 111, 112, 74, 70, 70, 67, 66, 66, 64, 63, 66,
+ 67, 70, 71, 74, 75, 78, 80, 82, 86, 87, 91, 92, 96, 98, 101, 104, 106,
+ 108, 108, 111, 111, 112, 113, 75, 71, 71, 68, 68, 67, 66, 64, 66, 68,
+ 70, 71, 74, 75, 79, 79, 84, 84, 88, 90, 93, 95, 98, 101, 103, 107, 108,
+ 110, 111, 113, 113, 115, 76, 72, 72, 69, 69, 68, 67, 65, 66, 69, 70, 72,
+ 74, 76, 78, 81, 83, 85, 88, 90, 93, 95, 98, 100, 104, 105, 109, 111,
+ 112, 113, 116, 115, 78, 74, 74, 70, 70, 69, 69, 66, 66, 70, 70, 74, 74,
+ 77, 78, 82, 82, 86, 87, 92, 92, 96, 97, 102, 102, 107, 107, 112, 113,
+ 115, 115, 118,
+
+ // Size 4x8
+ 31, 47, 60, 66, 40, 45, 54, 61, 46, 56, 64, 64, 48, 61, 75, 73, 54, 65,
+ 85, 82, 61, 69, 92, 92, 64, 68, 90, 102, 68, 71, 87, 105,
+
+ // Size 8x4
+ 31, 40, 46, 48, 54, 61, 64, 68, 47, 45, 56, 61, 65, 69, 68, 71, 60, 54,
+ 64, 75, 85, 92, 90, 87, 66, 61, 64, 73, 82, 92, 102, 105,
+
+ // Size 8x16
+ 32, 37, 48, 52, 57, 66, 68, 71, 30, 40, 46, 48, 52, 60, 63, 66, 33, 43,
+ 47, 47, 51, 59, 60, 63, 42, 47, 50, 50, 53, 60, 59, 62, 49, 48, 53, 54,
+ 57, 62, 62, 62, 49, 46, 53, 61, 64, 69, 66, 66, 50, 46, 54, 64, 67, 73,
+ 72, 70, 54, 49, 55, 68, 73, 80, 76, 75, 57, 50, 56, 70, 76, 84, 80, 79,
+ 63, 55, 60, 75, 82, 92, 87, 84, 64, 56, 61, 75, 83, 93, 93, 89, 68, 59,
+ 64, 74, 86, 94, 98, 94, 70, 62, 66, 73, 83, 96, 99, 98, 72, 64, 66, 75,
+ 83, 92, 101, 104, 74, 67, 66, 74, 84, 94, 103, 106, 76, 69, 67, 73, 82,
+ 91, 101, 109,
+
+ // Size 16x8
+ 32, 30, 33, 42, 49, 49, 50, 54, 57, 63, 64, 68, 70, 72, 74, 76, 37, 40,
+ 43, 47, 48, 46, 46, 49, 50, 55, 56, 59, 62, 64, 67, 69, 48, 46, 47, 50,
+ 53, 53, 54, 55, 56, 60, 61, 64, 66, 66, 66, 67, 52, 48, 47, 50, 54, 61,
+ 64, 68, 70, 75, 75, 74, 73, 75, 74, 73, 57, 52, 51, 53, 57, 64, 67, 73,
+ 76, 82, 83, 86, 83, 83, 84, 82, 66, 60, 59, 60, 62, 69, 73, 80, 84, 92,
+ 93, 94, 96, 92, 94, 91, 68, 63, 60, 59, 62, 66, 72, 76, 80, 87, 93, 98,
+ 99, 101, 103, 101, 71, 66, 63, 62, 62, 66, 70, 75, 79, 84, 89, 94, 98,
+ 104, 106, 109,
+
+ // Size 16x32
+ 32, 31, 37, 42, 48, 49, 52, 54, 57, 63, 66, 67, 68, 69, 71, 72, 31, 31,
+ 38, 42, 47, 47, 50, 52, 54, 60, 63, 64, 65, 66, 67, 68, 30, 32, 40, 42,
+ 46, 45, 48, 50, 52, 57, 60, 62, 63, 65, 66, 68, 32, 34, 41, 44, 46, 45,
+ 48, 49, 51, 57, 59, 61, 62, 63, 64, 65, 33, 36, 43, 45, 47, 46, 47, 49,
+ 51, 56, 59, 60, 60, 62, 63, 65, 37, 40, 47, 47, 47, 45, 47, 48, 50, 54,
+ 57, 58, 60, 61, 62, 63, 42, 43, 47, 48, 50, 49, 50, 52, 53, 57, 60, 58,
+ 59, 60, 62, 63, 45, 44, 47, 49, 51, 51, 52, 54, 55, 59, 61, 61, 61, 60,
+ 61, 61, 49, 46, 48, 50, 53, 53, 54, 55, 57, 60, 62, 63, 62, 63, 62, 62,
+ 48, 46, 47, 50, 53, 56, 57, 59, 60, 64, 66, 65, 65, 64, 64, 65, 49, 45,
+ 46, 49, 53, 58, 61, 62, 64, 67, 69, 67, 66, 66, 66, 65, 49, 46, 46, 49,
+ 53, 59, 62, 64, 65, 69, 71, 70, 68, 68, 67, 68, 50, 46, 46, 50, 54, 59,
+ 64, 65, 67, 71, 73, 72, 72, 70, 70, 69, 52, 48, 47, 50, 54, 61, 66, 68,
+ 71, 75, 77, 74, 73, 73, 71, 72, 54, 50, 49, 52, 55, 62, 68, 71, 73, 78,
+ 80, 78, 76, 74, 75, 73, 55, 51, 49, 52, 56, 63, 69, 72, 75, 80, 82, 80,
+ 79, 78, 76, 77, 57, 52, 50, 53, 56, 64, 70, 73, 76, 82, 84, 82, 80, 80,
+ 79, 77, 60, 54, 52, 55, 58, 65, 72, 75, 79, 85, 88, 86, 84, 82, 81, 81,
+ 63, 57, 55, 58, 60, 67, 75, 78, 82, 89, 92, 88, 87, 85, 84, 81, 64, 58,
+ 55, 58, 61, 68, 75, 78, 82, 89, 92, 90, 89, 87, 86, 86, 64, 59, 56, 58,
+ 61, 68, 75, 79, 83, 90, 93, 95, 93, 91, 89, 87, 67, 61, 58, 60, 63, 69,
+ 76, 79, 85, 92, 95, 96, 94, 92, 91, 91, 68, 62, 59, 60, 64, 71, 74, 78,
+ 86, 91, 94, 96, 98, 96, 94, 91, 69, 62, 60, 60, 65, 70, 72, 79, 85, 88,
+ 95, 98, 99, 98, 97, 96, 70, 63, 62, 60, 66, 69, 73, 81, 83, 89, 96, 97,
+ 99, 101, 98, 97, 71, 64, 63, 61, 67, 68, 74, 79, 82, 90, 93, 98, 102,
+ 102, 102, 101, 72, 65, 64, 62, 66, 68, 75, 78, 83, 89, 92, 100, 101,
+ 103, 104, 102, 73, 66, 65, 63, 66, 69, 75, 76, 84, 87, 93, 98, 102, 105,
+ 106, 107, 74, 67, 67, 64, 66, 70, 74, 77, 84, 86, 94, 96, 103, 105, 106,
+ 107, 75, 68, 68, 65, 66, 71, 74, 78, 83, 87, 93, 96, 103, 105, 109, 109,
+ 76, 69, 69, 66, 67, 72, 73, 80, 82, 88, 91, 97, 101, 107, 109, 110, 77,
+ 70, 70, 67, 67, 73, 73, 81, 81, 90, 90, 99, 99, 108, 108, 113,
+
+ // Size 32x16
+ 32, 31, 30, 32, 33, 37, 42, 45, 49, 48, 49, 49, 50, 52, 54, 55, 57, 60,
+ 63, 64, 64, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 31, 31, 32, 34,
+ 36, 40, 43, 44, 46, 46, 45, 46, 46, 48, 50, 51, 52, 54, 57, 58, 59, 61,
+ 62, 62, 63, 64, 65, 66, 67, 68, 69, 70, 37, 38, 40, 41, 43, 47, 47, 47,
+ 48, 47, 46, 46, 46, 47, 49, 49, 50, 52, 55, 55, 56, 58, 59, 60, 62, 63,
+ 64, 65, 67, 68, 69, 70, 42, 42, 42, 44, 45, 47, 48, 49, 50, 50, 49, 49,
+ 50, 50, 52, 52, 53, 55, 58, 58, 58, 60, 60, 60, 60, 61, 62, 63, 64, 65,
+ 66, 67, 48, 47, 46, 46, 47, 47, 50, 51, 53, 53, 53, 53, 54, 54, 55, 56,
+ 56, 58, 60, 61, 61, 63, 64, 65, 66, 67, 66, 66, 66, 66, 67, 67, 49, 47,
+ 45, 45, 46, 45, 49, 51, 53, 56, 58, 59, 59, 61, 62, 63, 64, 65, 67, 68,
+ 68, 69, 71, 70, 69, 68, 68, 69, 70, 71, 72, 73, 52, 50, 48, 48, 47, 47,
+ 50, 52, 54, 57, 61, 62, 64, 66, 68, 69, 70, 72, 75, 75, 75, 76, 74, 72,
+ 73, 74, 75, 75, 74, 74, 73, 73, 54, 52, 50, 49, 49, 48, 52, 54, 55, 59,
+ 62, 64, 65, 68, 71, 72, 73, 75, 78, 78, 79, 79, 78, 79, 81, 79, 78, 76,
+ 77, 78, 80, 81, 57, 54, 52, 51, 51, 50, 53, 55, 57, 60, 64, 65, 67, 71,
+ 73, 75, 76, 79, 82, 82, 83, 85, 86, 85, 83, 82, 83, 84, 84, 83, 82, 81,
+ 63, 60, 57, 57, 56, 54, 57, 59, 60, 64, 67, 69, 71, 75, 78, 80, 82, 85,
+ 89, 89, 90, 92, 91, 88, 89, 90, 89, 87, 86, 87, 88, 90, 66, 63, 60, 59,
+ 59, 57, 60, 61, 62, 66, 69, 71, 73, 77, 80, 82, 84, 88, 92, 92, 93, 95,
+ 94, 95, 96, 93, 92, 93, 94, 93, 91, 90, 67, 64, 62, 61, 60, 58, 58, 61,
+ 63, 65, 67, 70, 72, 74, 78, 80, 82, 86, 88, 90, 95, 96, 96, 98, 97, 98,
+ 100, 98, 96, 96, 97, 99, 68, 65, 63, 62, 60, 60, 59, 61, 62, 65, 66, 68,
+ 72, 73, 76, 79, 80, 84, 87, 89, 93, 94, 98, 99, 99, 102, 101, 102, 103,
+ 103, 101, 99, 69, 66, 65, 63, 62, 61, 60, 60, 63, 64, 66, 68, 70, 73,
+ 74, 78, 80, 82, 85, 87, 91, 92, 96, 98, 101, 102, 103, 105, 105, 105,
+ 107, 108, 71, 67, 66, 64, 63, 62, 62, 61, 62, 64, 66, 67, 70, 71, 75,
+ 76, 79, 81, 84, 86, 89, 91, 94, 97, 98, 102, 104, 106, 106, 109, 109,
+ 108, 72, 68, 68, 65, 65, 63, 63, 61, 62, 65, 65, 68, 69, 72, 73, 77, 77,
+ 81, 81, 86, 87, 91, 91, 96, 97, 101, 102, 107, 107, 109, 110, 113,
+
+ // Size 4x16
+ 31, 49, 63, 69, 32, 45, 57, 65, 36, 46, 56, 62, 43, 49, 57, 60, 46, 53,
+ 60, 63, 45, 58, 67, 66, 46, 59, 71, 70, 50, 62, 78, 74, 52, 64, 82, 80,
+ 57, 67, 89, 85, 59, 68, 90, 91, 62, 71, 91, 96, 63, 69, 89, 101, 65, 68,
+ 89, 103, 67, 70, 86, 105, 69, 72, 88, 107,
+
+ // Size 16x4
+ 31, 32, 36, 43, 46, 45, 46, 50, 52, 57, 59, 62, 63, 65, 67, 69, 49, 45,
+ 46, 49, 53, 58, 59, 62, 64, 67, 68, 71, 69, 68, 70, 72, 63, 57, 56, 57,
+ 60, 67, 71, 78, 82, 89, 90, 91, 89, 89, 86, 88, 69, 65, 62, 60, 63, 66,
+ 70, 74, 80, 85, 91, 96, 101, 103, 105, 107,
+
+ // Size 8x32
+ 32, 37, 48, 52, 57, 66, 68, 71, 31, 38, 47, 50, 54, 63, 65, 67, 30, 40,
+ 46, 48, 52, 60, 63, 66, 32, 41, 46, 48, 51, 59, 62, 64, 33, 43, 47, 47,
+ 51, 59, 60, 63, 37, 47, 47, 47, 50, 57, 60, 62, 42, 47, 50, 50, 53, 60,
+ 59, 62, 45, 47, 51, 52, 55, 61, 61, 61, 49, 48, 53, 54, 57, 62, 62, 62,
+ 48, 47, 53, 57, 60, 66, 65, 64, 49, 46, 53, 61, 64, 69, 66, 66, 49, 46,
+ 53, 62, 65, 71, 68, 67, 50, 46, 54, 64, 67, 73, 72, 70, 52, 47, 54, 66,
+ 71, 77, 73, 71, 54, 49, 55, 68, 73, 80, 76, 75, 55, 49, 56, 69, 75, 82,
+ 79, 76, 57, 50, 56, 70, 76, 84, 80, 79, 60, 52, 58, 72, 79, 88, 84, 81,
+ 63, 55, 60, 75, 82, 92, 87, 84, 64, 55, 61, 75, 82, 92, 89, 86, 64, 56,
+ 61, 75, 83, 93, 93, 89, 67, 58, 63, 76, 85, 95, 94, 91, 68, 59, 64, 74,
+ 86, 94, 98, 94, 69, 60, 65, 72, 85, 95, 99, 97, 70, 62, 66, 73, 83, 96,
+ 99, 98, 71, 63, 67, 74, 82, 93, 102, 102, 72, 64, 66, 75, 83, 92, 101,
+ 104, 73, 65, 66, 75, 84, 93, 102, 106, 74, 67, 66, 74, 84, 94, 103, 106,
+ 75, 68, 66, 74, 83, 93, 103, 109, 76, 69, 67, 73, 82, 91, 101, 109, 77,
+ 70, 67, 73, 81, 90, 99, 108,
+
+ // Size 32x8
+ 32, 31, 30, 32, 33, 37, 42, 45, 49, 48, 49, 49, 50, 52, 54, 55, 57, 60,
+ 63, 64, 64, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 37, 38, 40, 41,
+ 43, 47, 47, 47, 48, 47, 46, 46, 46, 47, 49, 49, 50, 52, 55, 55, 56, 58,
+ 59, 60, 62, 63, 64, 65, 67, 68, 69, 70, 48, 47, 46, 46, 47, 47, 50, 51,
+ 53, 53, 53, 53, 54, 54, 55, 56, 56, 58, 60, 61, 61, 63, 64, 65, 66, 67,
+ 66, 66, 66, 66, 67, 67, 52, 50, 48, 48, 47, 47, 50, 52, 54, 57, 61, 62,
+ 64, 66, 68, 69, 70, 72, 75, 75, 75, 76, 74, 72, 73, 74, 75, 75, 74, 74,
+ 73, 73, 57, 54, 52, 51, 51, 50, 53, 55, 57, 60, 64, 65, 67, 71, 73, 75,
+ 76, 79, 82, 82, 83, 85, 86, 85, 83, 82, 83, 84, 84, 83, 82, 81, 66, 63,
+ 60, 59, 59, 57, 60, 61, 62, 66, 69, 71, 73, 77, 80, 82, 84, 88, 92, 92,
+ 93, 95, 94, 95, 96, 93, 92, 93, 94, 93, 91, 90, 68, 65, 63, 62, 60, 60,
+ 59, 61, 62, 65, 66, 68, 72, 73, 76, 79, 80, 84, 87, 89, 93, 94, 98, 99,
+ 99, 102, 101, 102, 103, 103, 101, 99, 71, 67, 66, 64, 63, 62, 62, 61,
+ 62, 64, 66, 67, 70, 71, 75, 76, 79, 81, 84, 86, 89, 91, 94, 97, 98, 102,
+ 104, 106, 106, 109, 109, 108
+ ]
+ ]
+ ];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs
new file mode 100644
index 0000000000..205f1f3a19
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs
@@ -0,0 +1,190 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
+
+internal class Av1QuantizationLookup
+{
+ // Coefficient scaling and quantization with AV1 TX are tailored to
+ // the AV1 TX transforms. Regardless of the bit-depth of the input,
+ // the transform stages scale the coefficient values up by a factor of
+ // 8 (3 bits) over the scale of the pixel values. Thus, for 8-bit
+ // input, the coefficients have effectively 11 bits of scale depth
+ // (8+3), 10-bit input pixels result in 13-bit coefficient depth
+ // (10+3) and 12-bit pixels yield 15-bit (12+3) coefficient depth.
+ // All quantizers are built using this invariant of x8, 3-bit scaling,
+ // thus the Q3 suffix.
+
+ // A partial exception to this rule is large transforms; to avoid
+ // overflow, TX blocks with > 256 pels (>16x16) are scaled only
+ // 4-times unity (2 bits) over the pixel depth, and TX blocks with
+ // over 1024 pixels (>32x32) are scaled up only 2x unity (1 bit).
+ // This descaling is found via av1_tx_get_scale(). Thus, 16x32, 32x16
+ // and 32x32 transforms actually return Q2 coefficients, and 32x64,
+ // 64x32 and 64x64 transforms return Q1 coefficients. However, the
+ // quantizers are de-scaled down on-the-fly by the same amount
+ // (av1_tx_get_scale()) during quantization, and as such the
+ // dequantized/decoded coefficients, even for large TX blocks, are always
+ // effectively Q3. Meanwhile, quantized/coded coefficients are Q0
+ // because Qn quantizers are applied to Qn tx coefficients.
+
+ // Note that encoder decision making (which uses the quantizer to
+ // generate several bespoke lamdas for RDO and other heuristics)
+ // expects quantizers to be larger for higher-bitdepth input. In
+ // addition, the minimum allowable quantizer is 4; smaller values will
+ // underflow to 0 in the actual quantization routines.
+ private static readonly short[] AcQlookup8 = [
+ 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
+ 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101,
+ 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138,
+ 140, 142, 144, 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 182, 185, 188,
+ 191, 194, 197, 200, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, 260,
+ 265, 270, 275, 280, 285, 290, 295, 300, 305, 311, 317, 323, 329, 335, 341, 347, 353, 359, 366,
+ 373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448, 456, 465, 474, 483, 492, 501, 510, 520,
+ 530, 540, 550, 560, 571, 582, 593, 604, 615, 627, 639, 651, 663, 676, 689, 702, 715, 729, 743,
+ 757, 771, 786, 801, 816, 832, 848, 864, 881, 898, 915, 933, 951, 969, 988, 1007, 1026, 1046, 1066,
+ 1087, 1108, 1129, 1151, 1173, 1196, 1219, 1243, 1267, 1292, 1317, 1343, 1369, 1396, 1423, 1451, 1479, 1508, 1537,
+ 1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828,
+ ];
+
+ private static readonly short[] AcQlookup10 = [
+ 4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63,
+ 67, 71, 75, 79, 83, 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145,
+ 149, 154, 158, 163, 168, 172, 177, 181, 186, 190, 195, 199, 204, 208, 213, 217, 222, 226, 231,
+ 235, 240, 244, 249, 253, 258, 262, 267, 271, 275, 280, 284, 289, 293, 297, 302, 306, 311, 315,
+ 319, 324, 328, 332, 337, 341, 345, 349, 354, 358, 362, 367, 371, 375, 379, 384, 388, 392, 396,
+ 401, 409, 417, 425, 433, 441, 449, 458, 466, 474, 482, 490, 498, 506, 514, 523, 531, 539, 547,
+ 555, 563, 571, 579, 588, 596, 604, 616, 628, 640, 652, 664, 676, 688, 700, 713, 725, 737, 749,
+ 761, 773, 785, 797, 809, 825, 841, 857, 873, 889, 905, 922, 938, 954, 970, 986, 1002, 1018, 1038,
+ 1058, 1078, 1098, 1118, 1138, 1158, 1178, 1198, 1218, 1242, 1266, 1290, 1314, 1338, 1362, 1386, 1411, 1435, 1463,
+ 1491, 1519, 1547, 1575, 1603, 1631, 1663, 1695, 1727, 1759, 1791, 1823, 1859, 1895, 1931, 1967, 2003, 2039, 2079,
+ 2119, 2159, 2199, 2239, 2283, 2327, 2371, 2415, 2459, 2507, 2555, 2603, 2651, 2703, 2755, 2807, 2859, 2915, 2971,
+ 3027, 3083, 3143, 3203, 3263, 3327, 3391, 3455, 3523, 3591, 3659, 3731, 3803, 3876, 3952, 4028, 4104, 4184, 4264,
+ 4348, 4432, 4516, 4604, 4692, 4784, 4876, 4972, 5068, 5168, 5268, 5372, 5476, 5584, 5692, 5804, 5916, 6032, 6148,
+ 6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172, 7312,
+ ];
+
+ private static readonly short[] AcQlookup12 = [
+ 4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168,
+ 183, 199, 214, 230, 247, 263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438,
+ 456, 475, 493, 511, 530, 548, 567, 586, 604, 623, 642, 660, 679, 698, 716, 735,
+ 753, 772, 791, 809, 828, 846, 865, 884, 902, 920, 939, 957, 976, 994, 1012, 1030,
+ 1049, 1067, 1085, 1103, 1121, 1139, 1157, 1175, 1193, 1211, 1229, 1246, 1264, 1282, 1299, 1317,
+ 1335, 1352, 1370, 1387, 1405, 1422, 1440, 1457, 1474, 1491, 1509, 1526, 1543, 1560, 1577, 1595,
+ 1627, 1660, 1693, 1725, 1758, 1791, 1824, 1856, 1889, 1922, 1954, 1987, 2020, 2052, 2085, 2118,
+ 2150, 2183, 2216, 2248, 2281, 2313, 2346, 2378, 2411, 2459, 2508, 2556, 2605, 2653, 2701, 2750,
+ 2798, 2847, 2895, 2943, 2992, 3040, 3088, 3137, 3185, 3234, 3298, 3362, 3426, 3491, 3555, 3619,
+ 3684, 3748, 3812, 3876, 3941, 4005, 4069, 4149, 4230, 4310, 4390, 4470, 4550, 4631, 4711, 4791,
+ 4871, 4967, 5064, 5160, 5256, 5352, 5448, 5544, 5641, 5737, 5849, 5961, 6073, 6185, 6297, 6410,
+ 6522, 6650, 6778, 6906, 7034, 7162, 7290, 7435, 7579, 7723, 7867, 8011, 8155, 8315, 8475, 8635,
+ 8795, 8956, 9132, 9308, 9484, 9660, 9836, 10028, 10220, 10412, 10604, 10812, 11020, 11228, 11437, 11661,
+ 11885, 12109, 12333, 12573, 12813, 13053, 13309, 13565, 13821, 14093, 14365, 14637, 14925, 15213, 15502, 15806,
+ 16110, 16414, 16734, 17054, 17390, 17726, 18062, 18414, 18766, 19134, 19502, 19886, 20270, 20670, 21070, 21486,
+ 21902, 22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143, 28687, 29247,
+ ];
+
+ private static readonly short[] DcQlookup8 = [
+ 4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23,
+ 24, 25, 26, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40,
+ 41, 42, 43, 43, 44, 45, 46, 47, 48, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57,
+ 57, 58, 59, 60, 61, 62, 62, 63, 64, 65, 66, 66, 67, 68, 69, 70, 70, 71, 72, 73,
+ 74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 85, 87, 88, 90, 92,
+ 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117, 118, 120, 121,
+ 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 161, 164,
+ 166, 169, 172, 174, 177, 180, 182, 185, 187, 190, 192, 195, 199, 202, 205, 208, 211, 214, 217, 220,
+ 223, 226, 230, 233, 237, 240, 243, 247, 250, 253, 257, 261, 265, 269, 272, 276, 280, 284, 288, 292,
+ 296, 300, 304, 309, 313, 317, 322, 326, 330, 335, 340, 344, 349, 354, 359, 364, 369, 374, 379, 384,
+ 389, 395, 400, 406, 411, 417, 423, 429, 435, 441, 447, 454, 461, 467, 475, 482, 489, 497, 505, 513,
+ 522, 530, 539, 549, 559, 569, 579, 590, 602, 614, 626, 640, 654, 668, 684, 700, 717, 736, 755, 775,
+ 796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, 1184, 1232, 1282, 1336,
+ ];
+
+ private static readonly short[] DcQlookup10 = [
+ 4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57,
+ 60, 64, 68, 71, 75, 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128,
+ 132, 136, 140, 143, 147, 151, 155, 159, 163, 166, 170, 174, 178, 182, 185, 189, 193, 197, 200,
+ 204, 208, 212, 215, 219, 223, 226, 230, 233, 237, 241, 244, 248, 251, 255, 259, 262, 266, 269,
+ 273, 276, 280, 283, 287, 290, 293, 297, 300, 304, 307, 310, 314, 317, 321, 324, 327, 331, 334,
+ 337, 343, 350, 356, 362, 369, 375, 381, 387, 394, 400, 406, 412, 418, 424, 430, 436, 442, 448,
+ 454, 460, 466, 472, 478, 484, 490, 499, 507, 516, 525, 533, 542, 550, 559, 567, 576, 584, 592,
+ 601, 609, 617, 625, 634, 644, 655, 666, 676, 687, 698, 708, 718, 729, 739, 749, 759, 770, 782,
+ 795, 807, 819, 831, 844, 856, 868, 880, 891, 906, 920, 933, 947, 961, 975, 988, 1001, 1015, 1030,
+ 1045, 1061, 1076, 1090, 1105, 1120, 1137, 1153, 1170, 1186, 1202, 1218, 1236, 1253, 1271, 1288, 1306, 1323, 1342,
+ 1361, 1379, 1398, 1416, 1436, 1456, 1476, 1496, 1516, 1537, 1559, 1580, 1601, 1624, 1647, 1670, 1692, 1717, 1741,
+ 1766, 1791, 1817, 1844, 1871, 1900, 1929, 1958, 1990, 2021, 2054, 2088, 2123, 2159, 2197, 2236, 2276, 2319, 2363,
+ 2410, 2458, 2508, 2561, 2616, 2675, 2737, 2802, 2871, 2944, 3020, 3102, 3188, 3280, 3375, 3478, 3586, 3702, 3823,
+ 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347,
+ ];
+
+ private static readonly short[] DcQlookup12 = [
+ 4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153,
+ 166, 180, 194, 208, 222, 237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390,
+ 405, 421, 437, 453, 469, 484, 500, 516, 532, 548, 564, 580, 596, 611, 627, 643,
+ 659, 674, 690, 706, 721, 737, 752, 768, 783, 798, 814, 829, 844, 859, 874, 889,
+ 904, 919, 934, 949, 964, 978, 993, 1008, 1022, 1037, 1051, 1065, 1080, 1094, 1108, 1122,
+ 1136, 1151, 1165, 1179, 1192, 1206, 1220, 1234, 1248, 1261, 1275, 1288, 1302, 1315, 1329, 1342,
+ 1368, 1393, 1419, 1444, 1469, 1494, 1519, 1544, 1569, 1594, 1618, 1643, 1668, 1692, 1717, 1741,
+ 1765, 1789, 1814, 1838, 1862, 1885, 1909, 1933, 1957, 1992, 2027, 2061, 2096, 2130, 2165, 2199,
+ 2233, 2267, 2300, 2334, 2367, 2400, 2434, 2467, 2499, 2532, 2575, 2618, 2661, 2704, 2746, 2788,
+ 2830, 2872, 2913, 2954, 2995, 3036, 3076, 3127, 3177, 3226, 3275, 3324, 3373, 3421, 3469, 3517,
+ 3565, 3621, 3677, 3733, 3788, 3843, 3897, 3951, 4005, 4058, 4119, 4181, 4241, 4301, 4361, 4420,
+ 4479, 4546, 4612, 4677, 4742, 4807, 4871, 4942, 5013, 5083, 5153, 5222, 5291, 5367, 5442, 5517,
+ 5591, 5665, 5745, 5825, 5905, 5984, 6063, 6149, 6234, 6319, 6404, 6495, 6587, 6678, 6769, 6867,
+ 6966, 7064, 7163, 7269, 7376, 7483, 7599, 7715, 7832, 7958, 8085, 8214, 8352, 8492, 8635, 8788,
+ 8945, 9104, 9275, 9450, 9639, 9832, 10031, 10245, 10465, 10702, 10946, 11210, 11482, 11776, 12081, 12409,
+ 12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943, 17575, 18237, 18949, 19718, 20521, 21387,
+ ];
+
+ public static short GetDcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth)
+ {
+ int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ);
+ switch (bitDepth)
+ {
+ case Av1BitDepth.EightBit:
+ return DcQlookup8[qClamped];
+ case Av1BitDepth.TenBit:
+ return DcQlookup10[qClamped];
+ case Av1BitDepth.TwelveBit:
+ return DcQlookup12[qClamped];
+ default:
+ Guard.IsFalse(true, nameof(bitDepth), "bit_depth should be EB_EIGHT_BIT, EB_TEN_BIT or EB_TWELVE_BIT");
+ return -1;
+ }
+ }
+
+ public static short GetAcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth)
+ {
+ int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ);
+ switch (bitDepth)
+ {
+ case Av1BitDepth.EightBit:
+ return AcQlookup8[qClamped];
+ case Av1BitDepth.TenBit:
+ return AcQlookup10[qClamped];
+ case Av1BitDepth.TwelveBit:
+ return AcQlookup12[qClamped];
+ default:
+ Guard.IsFalse(true, nameof(bitDepth), "bit_depth should be EB_EIGHT_BIT, EB_TEN_BIT or EB_TWELVE_BIT");
+ return -1;
+ }
+ }
+
+ public static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex)
+ {
+ if (segmentationParameters.IsFeatureActive(segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer))
+ {
+ int data = segmentationParameters.FeatureData[segmentId, (int)ObuSegmentationLevelFeature.AlternativeQuantizer];
+ int qIndex = baseQIndex + data;
+ return Av1Math.Clamp(qIndex, 0, Av1Constants.MaxQ);
+ }
+ else
+ {
+ return baseQIndex;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1BottomRightTopLeftConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1BottomRightTopLeftConstants.cs
new file mode 100644
index 0000000000..a3a3e0bd8b
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1BottomRightTopLeftConstants.cs
@@ -0,0 +1,505 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1BottomRightTopLeftConstants
+{
+ // Tables to store if the top-right reference pixels are available. The flags
+ // are represented with bits, packed into 8-bit integers. E.g., for the 32x32
+ // blocks in a 128x128 superblock, the index of the "o" block is 10 (in raster
+ // order), so its flag is stored at the 3rd bit of the 2nd entry in the table,
+ // i.e. (table[10 / 8] >> (10 % 8)) & 1.
+ // . . . .
+ // . . . .
+ // . . o .
+ // . . . .
+ private static readonly byte[] HasTopRight4x4 = [
+ 255, 255, 255, 255, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 127, 127, 127, 127, 85, 85,
+ 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 255, 127, 255, 127, 85, 85, 85, 85, 119, 119, 119, 119,
+ 85, 85, 85, 85, 127, 127, 127, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 255, 255,
+ 255, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 127, 127, 127, 127, 85, 85, 85, 85,
+ 119, 119, 119, 119, 85, 85, 85, 85, 255, 127, 255, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85,
+ 85, 85, 127, 127, 127, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85,
+ ];
+
+ private static readonly byte[] HasTopRight4x8 = [
+ 255, 255, 255, 255, 119, 119, 119, 119, 127, 127, 127, 127, 119, 119, 119, 119, 255, 127, 255, 127, 119, 119,
+ 119, 119, 127, 127, 127, 127, 119, 119, 119, 119, 255, 255, 255, 127, 119, 119, 119, 119, 127, 127, 127, 127,
+ 119, 119, 119, 119, 255, 127, 255, 127, 119, 119, 119, 119, 127, 127, 127, 127, 119, 119, 119, 119,
+ ];
+
+ private static readonly byte[] HasTopRight8x4 = [
+ 255, 255, 0, 0, 85, 85, 0, 0, 119, 119, 0, 0, 85, 85, 0, 0, 127, 127, 0, 0, 85, 85,
+ 0, 0, 119, 119, 0, 0, 85, 85, 0, 0, 255, 127, 0, 0, 85, 85, 0, 0, 119, 119, 0, 0,
+ 85, 85, 0, 0, 127, 127, 0, 0, 85, 85, 0, 0, 119, 119, 0, 0, 85, 85, 0, 0,
+ ];
+
+ private static readonly byte[] HasTopRight8x8 = [
+ 255, 255, 85, 85, 119, 119, 85, 85, 127, 127, 85, 85, 119, 119, 85, 85,
+ 255, 127, 85, 85, 119, 119, 85, 85, 127, 127, 85, 85, 119, 119, 85, 85,
+ ];
+
+ private static readonly byte[] HasTopRight8x16 = [
+ 255,
+ 255,
+ 119,
+ 119,
+ 127,
+ 127,
+ 119,
+ 119,
+ 255,
+ 127,
+ 119,
+ 119,
+ 127,
+ 127,
+ 119,
+ 119,
+ ];
+
+ private static readonly byte[] HasTopRight16x8 = [
+ 255,
+ 0,
+ 85,
+ 0,
+ 119,
+ 0,
+ 85,
+ 0,
+ 127,
+ 0,
+ 85,
+ 0,
+ 119,
+ 0,
+ 85,
+ 0,
+ ];
+
+ private static readonly byte[] HasTopRight16x16 = [
+ 255,
+ 85,
+ 119,
+ 85,
+ 127,
+ 85,
+ 119,
+ 85,
+ ];
+
+ private static readonly byte[] HasTopRight16x32 = [255, 119, 127, 119];
+ private static readonly byte[] HasTopRight32x16 = [15, 5, 7, 5];
+ private static readonly byte[] HasTopRight32x32 = [95, 87];
+ private static readonly byte[] HasTopRight32x64 = [127];
+ private static readonly byte[] HasTopRight64x32 = [19];
+ private static readonly byte[] HasTopRight64x64 = [7];
+ private static readonly byte[] HasTopRight64x128 = [3];
+ private static readonly byte[] HasTopRight128x64 = [1];
+ private static readonly byte[] HasTopRight128x128 = [1];
+ private static readonly byte[] HasTopRight4x16 = [
+ 255, 255, 255, 255, 127, 127, 127, 127, 255, 127, 255, 127, 127, 127, 127, 127,
+ 255, 255, 255, 127, 127, 127, 127, 127, 255, 127, 255, 127, 127, 127, 127, 127,
+ ];
+
+ private static readonly byte[] HasTopRight16x4 = [
+ 255, 0, 0, 0, 85, 0, 0, 0, 119, 0, 0, 0, 85, 0, 0, 0, 127, 0, 0, 0, 85, 0, 0, 0, 119, 0, 0, 0, 85, 0, 0, 0,
+ ];
+
+ private static readonly byte[] HasTopRight8x32 = [
+ 255,
+ 255,
+ 127,
+ 127,
+ 255,
+ 127,
+ 127,
+ 127,
+ ];
+
+ private static readonly byte[] HasTopRight32x8 = [
+ 15,
+ 0,
+ 5,
+ 0,
+ 7,
+ 0,
+ 5,
+ 0,
+ ];
+
+ private static readonly byte[] HasTopRight16x64 = [255, 127];
+ private static readonly byte[] HasTopRight64x16 = [3, 1];
+
+ private static readonly byte[][] HasTopRightTables = [
+
+ // 4X4
+ HasTopRight4x4,
+
+ // 4X8, 8X4, 8X8
+ HasTopRight4x8,
+ HasTopRight8x4,
+ HasTopRight8x8,
+
+ // 8X16, 16X8, 16X16
+ HasTopRight8x16,
+ HasTopRight16x8,
+ HasTopRight16x16,
+
+ // 16X32, 32X16, 32X32
+ HasTopRight16x32,
+ HasTopRight32x16,
+ HasTopRight32x32,
+
+ // 32X64, 64X32, 64X64
+ HasTopRight32x64,
+ HasTopRight64x32,
+ HasTopRight64x64,
+
+ // 64x128, 128x64, 128x128
+ HasTopRight64x128,
+ HasTopRight128x64,
+ HasTopRight128x128,
+
+ // 4x16, 16x4, 8x32
+ HasTopRight4x16,
+ HasTopRight16x4,
+ HasTopRight8x32,
+
+ // 32x8, 16x64, 64x16
+ HasTopRight32x8,
+ HasTopRight16x64,
+ HasTopRight64x16
+ ];
+
+ private static readonly byte[] HasTopRightVertical8x8 = [
+ 255, 255, 0, 0, 119, 119, 0, 0, 127, 127, 0, 0, 119, 119, 0, 0,
+ 255, 127, 0, 0, 119, 119, 0, 0, 127, 127, 0, 0, 119, 119, 0, 0,
+ ];
+
+ private static readonly byte[] HasTopRightVertical16x16 = [
+ 255,
+ 0,
+ 119,
+ 0,
+ 127,
+ 0,
+ 119,
+ 0,
+ ];
+
+ private static readonly byte[] HasTopRightVertical32x32 = [15, 7];
+ private static readonly byte[] HasTopRightVertical64x64 = [3];
+
+ // The _vert_* tables are like the ordinary tables above, but describe the
+ // order we visit square blocks when doing a PARTITION_VERT_A or
+ // PARTITION_VERT_B. This is the same order as normal except for on the last
+ // split where we go vertically (TL, BL, TR, BR). We treat the rectangular block
+ // as a pair of squares, which means that these tables work correctly for both
+ // mixed vertical partition types.
+ //
+ // There are tables for each of the square sizes. Vertical rectangles (like
+ // BLOCK_16X32) use their respective "non-vert" table
+ private static readonly byte[]?[] HasTopRightVerticalTables = [
+
+ // 4X4
+ null,
+
+ // 4X8, 8X4, 8X8
+ HasTopRight4x8,
+ null,
+ HasTopRightVertical8x8,
+
+ // 8X16, 16X8, 16X16
+ HasTopRight8x16,
+ null,
+ HasTopRightVertical16x16,
+
+ // 16X32, 32X16, 32X32
+ HasTopRight16x32,
+ null,
+ HasTopRightVertical32x32,
+
+ // 32X64, 64X32, 64X64
+ HasTopRight32x64,
+ null,
+ HasTopRightVertical64x64,
+
+ // 64x128, 128x64, 128x128
+ HasTopRight64x128,
+ null,
+ HasTopRight128x128
+ ];
+
+ // Similar to the has_tr_* tables, but store if the bottom-left reference
+ // pixels are available.
+ private static readonly byte[] HasBottomLeft4x4 = [
+ 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85,
+ 85, 85, 0, 0, 1, 0, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85,
+ 16, 17, 17, 17, 84, 85, 85, 85, 0, 0, 0, 0, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 1,
+ 1, 1, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 0, 1, 0, 84, 85, 85, 85, 16, 17, 17, 17,
+ 84, 85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 0, 0, 0,
+ ];
+
+ private static readonly byte[] HasBottomLeft4x8 = [
+ 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 1, 0, 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 0, 0,
+ 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 1, 0, 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 0, 0,
+ ];
+
+ private static readonly byte[] HasBottomLeft8x4 = [
+ 254, 255, 84, 85, 254, 255, 16, 17, 254, 255, 84, 85, 254, 255, 0, 1, 254, 255, 84, 85, 254, 255,
+ 16, 17, 254, 255, 84, 85, 254, 255, 0, 0, 254, 255, 84, 85, 254, 255, 16, 17, 254, 255, 84, 85,
+ 254, 255, 0, 1, 254, 255, 84, 85, 254, 255, 16, 17, 254, 255, 84, 85, 254, 255, 0, 0,
+ ];
+
+ private static readonly byte[] HasBottomLeft8x8 = [
+ 84, 85, 16, 17, 84, 85, 0, 1, 84, 85, 16, 17, 84, 85, 0, 0,
+ 84, 85, 16, 17, 84, 85, 0, 1, 84, 85, 16, 17, 84, 85, 0, 0,
+ ];
+
+ private static readonly byte[] HasBottomLeft8x16 = [
+ 16,
+ 17,
+ 0,
+ 1,
+ 16,
+ 17,
+ 0,
+ 0,
+ 16,
+ 17,
+ 0,
+ 1,
+ 16,
+ 17,
+ 0,
+ 0,
+ ];
+
+ private static readonly byte[] HasBottomLeft16x8 = [
+ 254,
+ 84,
+ 254,
+ 16,
+ 254,
+ 84,
+ 254,
+ 0,
+ 254,
+ 84,
+ 254,
+ 16,
+ 254,
+ 84,
+ 254,
+ 0,
+ ];
+
+ private static readonly byte[] HasBottomLeft16x16 = [
+ 84,
+ 16,
+ 84,
+ 0,
+ 84,
+ 16,
+ 84,
+ 0,
+ ];
+
+ private static readonly byte[] HasBottomLeft16x32 = [16, 0, 16, 0];
+ private static readonly byte[] HasBottomLeft32x16 = [78, 14, 78, 14];
+ private static readonly byte[] HasBottomLeft32x32 = [4, 4];
+ private static readonly byte[] HasBottomLeft32x64 = [0];
+ private static readonly byte[] HasBottomLeft64x32 = [34];
+ private static readonly byte[] HasBottomLeft64x64 = [0];
+ private static readonly byte[] HasBottomLeft64x128 = [0];
+ private static readonly byte[] HasBottomLeft128x64 = [0];
+ private static readonly byte[] HasBottomLeft128x128 = [0];
+ private static readonly byte[] HasBottomLeft4x16 = [
+ 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0,
+ ];
+
+ private static readonly byte[] HasBottomLeft16x4 = [
+ 254, 254, 254, 84, 254, 254, 254, 16, 254, 254, 254, 84, 254, 254, 254, 0,
+ 254, 254, 254, 84, 254, 254, 254, 16, 254, 254, 254, 84, 254, 254, 254, 0,
+ ];
+
+ private static readonly byte[] HasBottomLeft8x32 = [
+ 0,
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ];
+
+ private static readonly byte[] HasBottomLeft32x8 = [
+ 238,
+ 78,
+ 238,
+ 14,
+ 238,
+ 78,
+ 238,
+ 14,
+ ];
+
+ private static readonly byte[] HasBottomLeft16x64 = [0, 0];
+ private static readonly byte[] HasBottomLeft64x16 = [42, 42];
+
+ private static readonly byte[][] HasBottomLeftTables = [
+
+ // 4X4
+ HasBottomLeft4x4,
+
+ // 4X8, 8X4, 8X8
+ HasBottomLeft4x8,
+ HasBottomLeft8x4,
+ HasBottomLeft8x8,
+
+ // 8X16, 16X8, 16X16
+ HasBottomLeft8x16,
+ HasBottomLeft16x8,
+ HasBottomLeft16x16,
+
+ // 16X32, 32X16, 32X32
+ HasBottomLeft16x32,
+ HasBottomLeft32x16,
+ HasBottomLeft32x32,
+
+ // 32X64, 64X32, 64X64
+ HasBottomLeft32x64,
+ HasBottomLeft64x32,
+ HasBottomLeft64x64,
+
+ // 64x128, 128x64, 128x128
+ HasBottomLeft64x128,
+ HasBottomLeft128x64,
+ HasBottomLeft128x128,
+
+ // 4x16, 16x4, 8x32
+ HasBottomLeft4x16,
+ HasBottomLeft16x4,
+ HasBottomLeft8x32,
+
+ // 32x8, 16x64, 64x16
+ HasBottomLeft32x8,
+ HasBottomLeft16x64,
+ HasBottomLeft64x16
+ ];
+
+ private static readonly byte[] HasBottomLeftVertical8x8 = [
+ 254, 255, 16, 17, 254, 255, 0, 1, 254, 255, 16, 17, 254, 255, 0, 0,
+ 254, 255, 16, 17, 254, 255, 0, 1, 254, 255, 16, 17, 254, 255, 0, 0,
+ ];
+
+ private static readonly byte[] HasBottomLeftVertical16x16 = [
+ 254,
+ 16,
+ 254,
+ 0,
+ 254,
+ 16,
+ 254,
+ 0,
+ ];
+
+ private static readonly byte[] HasBottomLeftVertical32x32 = [14, 14];
+ private static readonly byte[] HasBottomLeftVertical64x64 = [2];
+
+ // The _vert_* tables are like the ordinary tables above, but describe the
+ // order we visit square blocks when doing a PARTITION_VERT_A or
+ // PARTITION_VERT_B. This is the same order as normal except for on the last
+ // split where we go vertically (TL, BL, TR, BR). We treat the rectangular block
+ // as a pair of squares, which means that these tables work correctly for both
+ // mixed vertical partition types.
+ //
+ // There are tables for each of the square sizes. Vertical rectangles (like
+ // BLOCK_16X32) use their respective "non-vert" table
+ private static readonly byte[]?[] HasBottomLeftVerticalTables = [
+
+ // 4X4
+ null,
+
+ // 4X8, 8X4, 8X8
+ HasBottomLeft4x8,
+ null,
+ HasBottomLeftVertical8x8,
+
+ // 8X16, 16X8, 16X16
+ HasBottomLeft8x16,
+ null,
+ HasBottomLeftVertical16x16,
+
+ // 16X32, 32X16, 32X32
+ HasBottomLeft16x32,
+ null,
+ HasBottomLeftVertical32x32,
+
+ // 32X64, 64X32, 64X64
+ HasBottomLeft32x64,
+ null,
+ HasBottomLeftVertical64x64,
+
+ // 64x128, 128x64, 128x128
+ HasBottomLeft64x128,
+ null,
+ HasBottomLeft128x128];
+
+ public static bool HasTopRight(Av1PartitionType partitionType, Av1BlockSize blockSize, int blockIndex)
+ {
+ int index1 = blockIndex / 8;
+ int index2 = blockIndex % 8;
+ Span hasBottomLeftTable = GetHasTopRightTable(partitionType, blockSize);
+ return ((hasBottomLeftTable[index1] >> index2) & 1) > 0;
+ }
+
+ public static bool HasBottomLeft(Av1PartitionType partitionType, Av1BlockSize blockSize, int blockIndex)
+ {
+ int index1 = blockIndex / 8;
+ int index2 = blockIndex % 8;
+ Span hasBottomLeftTable = GetHasBottomLeftTable(partitionType, blockSize);
+ return ((hasBottomLeftTable[index1] >> index2) & 1) > 0;
+ }
+
+ private static Span GetHasTopRightTable(Av1PartitionType partition, Av1BlockSize blockSize)
+ {
+ byte[]? ret;
+
+ // If this is a mixed vertical partition, look up block size in vertical order.
+ if (partition is Av1PartitionType.VerticalA or Av1PartitionType.VerticalB)
+ {
+ DebugGuard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.SizeS, nameof(blockSize));
+ ret = HasTopRightVerticalTables[(int)blockSize];
+ }
+ else
+ {
+ ret = HasTopRightTables[(int)blockSize];
+ }
+
+ DebugGuard.NotNull(ret, nameof(ret));
+ return ret;
+ }
+
+ private static Span GetHasBottomLeftTable(Av1PartitionType partition, Av1BlockSize blockSize)
+ {
+ byte[]? ret;
+
+ // If this is a mixed vertical partition, look up block size in vertical order.
+ if (partition is Av1PartitionType.VerticalA or Av1PartitionType.VerticalB)
+ {
+ DebugGuard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.SizeS, nameof(blockSize));
+ ret = HasBottomLeftVerticalTables[(int)blockSize];
+ }
+ else
+ {
+ ret = HasBottomLeftTables[(int)blockSize];
+ }
+
+ DebugGuard.NotNull(ret, nameof(ret));
+ return ret;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs
new file mode 100644
index 0000000000..719d574b87
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1DcFillPredictor : IAv1Predictor
+{
+ private readonly uint blockWidth;
+ private readonly uint blockHeight;
+
+ public Av1DcFillPredictor(Size blockSize)
+ {
+ this.blockWidth = (uint)blockSize.Width;
+ this.blockHeight = (uint)blockSize.Height;
+ }
+
+ public Av1DcFillPredictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (uint)transformSize.GetWidth();
+ this.blockHeight = (uint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left)
+ => new Av1DcFillPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left);
+
+ public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left)
+ {
+ const byte expectedDc = 0x80;
+ for (uint r = 0; r < this.blockHeight; r++)
+ {
+ Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth);
+ destination = ref Unsafe.Add(ref destination, stride);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs
new file mode 100644
index 0000000000..1983396f17
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1DcLeftPredictor : IAv1Predictor
+{
+ private readonly uint blockWidth;
+ private readonly uint blockHeight;
+
+ public Av1DcLeftPredictor(Size blockSize)
+ {
+ this.blockWidth = (uint)blockSize.Width;
+ this.blockHeight = (uint)blockSize.Height;
+ }
+
+ public Av1DcLeftPredictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (uint)transformSize.GetWidth();
+ this.blockHeight = (uint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left)
+ => new Av1DcLeftPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left);
+
+ public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left)
+ {
+ int sum = 0;
+ for (uint i = 0; i < this.blockHeight; i++)
+ {
+ sum += Unsafe.Add(ref left, i);
+ }
+
+ byte expectedDc = (byte)((sum + (this.blockHeight >> 1)) / this.blockHeight);
+ for (uint r = 0; r < this.blockHeight; r++)
+ {
+ Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth);
+ destination = ref Unsafe.Add(ref destination, stride);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs
new file mode 100644
index 0000000000..51eef4b783
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1DcPredictor : IAv1Predictor
+{
+ private readonly uint blockWidth;
+ private readonly uint blockHeight;
+
+ public Av1DcPredictor(Size blockSize)
+ {
+ this.blockWidth = (uint)blockSize.Width;
+ this.blockHeight = (uint)blockSize.Height;
+ }
+
+ public Av1DcPredictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (uint)transformSize.GetWidth();
+ this.blockHeight = (uint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left)
+ => new Av1DcPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left);
+
+ public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left)
+ {
+ int sum = 0;
+ uint count = this.blockWidth + this.blockHeight;
+ for (uint i = 0; i < this.blockWidth; i++)
+ {
+ sum += Unsafe.Add(ref above, i);
+ }
+
+ for (uint i = 0; i < this.blockHeight; i++)
+ {
+ sum += Unsafe.Add(ref left, i);
+ }
+
+ byte expectedDc = (byte)((sum + (count >> 1)) / count);
+ for (uint r = 0; r < this.blockHeight; r++)
+ {
+ Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth);
+ destination = ref Unsafe.Add(ref destination, stride);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs
new file mode 100644
index 0000000000..d429b64549
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1DcTopPredictor : IAv1Predictor
+{
+ private readonly uint blockWidth;
+ private readonly uint blockHeight;
+
+ public Av1DcTopPredictor(Size blockSize)
+ {
+ this.blockWidth = (uint)blockSize.Width;
+ this.blockHeight = (uint)blockSize.Height;
+ }
+
+ public Av1DcTopPredictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (uint)transformSize.GetWidth();
+ this.blockHeight = (uint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left)
+ => new Av1DcTopPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left);
+
+ public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left)
+ {
+ int sum = 0;
+ for (uint i = 0; i < this.blockWidth; i++)
+ {
+ sum += Unsafe.Add(ref above, i);
+ }
+
+ byte expectedDc = (byte)((sum + (this.blockWidth >> 1)) / this.blockWidth);
+ for (uint r = 0; r < this.blockHeight; r++)
+ {
+ Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth);
+ destination = ref Unsafe.Add(ref destination, stride);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs
new file mode 100644
index 0000000000..81408fad31
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+[Flags]
+internal enum Av1NeighborNeed
+{
+ Nothing = 0,
+ Left = 2,
+ Above = 4,
+ AboveRight = 8,
+ AboveLeft = 16,
+ BottomLeft = 32,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
new file mode 100644
index 0000000000..f9ceab2b83
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
@@ -0,0 +1,1078 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1PredictionDecoder
+{
+ private const int MaxUpsampleSize = 16;
+
+ private readonly ObuSequenceHeader sequenceHeader;
+ private readonly ObuFrameHeader frameHeader;
+ private readonly bool is16BitPipeline;
+
+ public Av1PredictionDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool is16BitPipeline)
+ {
+ this.sequenceHeader = sequenceHeader;
+ this.frameHeader = frameHeader;
+ this.is16BitPipeline = is16BitPipeline;
+ }
+
+ public void Decode(
+ Av1PartitionInfo partitionInfo,
+ Av1Plane plane,
+ Av1TransformSize transformSize,
+ Av1TileInfo tileInfo,
+ Span pixelBuffer,
+ int pixelStride,
+ Av1BitDepth bitDepth,
+ int blockModeInfoColumnOffset,
+ int blockModeInfoRowOffset)
+ {
+ int bytesPerPixel = (bitDepth == Av1BitDepth.EightBit && !this.is16BitPipeline) ? 2 : 1;
+ ref byte pixelRef = ref pixelBuffer[0];
+ ref byte topNeighbor = ref pixelRef;
+ ref byte leftNeighbor = ref pixelRef;
+ int stride = pixelStride * bytesPerPixel;
+ topNeighbor = Unsafe.Subtract(ref topNeighbor, stride);
+ leftNeighbor = Unsafe.Subtract(ref leftNeighbor, 1);
+
+ bool is16BitPipeline = this.is16BitPipeline;
+ Av1PredictionMode mode = (plane == Av1Plane.Y) ? partitionInfo.ModeInfo.YMode : partitionInfo.ModeInfo.UvMode;
+
+ if (plane != Av1Plane.Y && partitionInfo.ModeInfo.UvMode == Av1PredictionMode.UvChromaFromLuma)
+ {
+ this.PredictIntraBlock(
+ partitionInfo,
+ plane,
+ transformSize,
+ tileInfo,
+ ref pixelRef,
+ stride,
+ ref topNeighbor,
+ ref leftNeighbor,
+ stride,
+ mode,
+ blockModeInfoColumnOffset,
+ blockModeInfoRowOffset,
+ bitDepth);
+
+ this.PredictChromaFromLumaBlock(
+ partitionInfo,
+ partitionInfo.ChromaFromLumaContext,
+ ref pixelBuffer,
+ stride,
+ transformSize,
+ plane);
+
+ return;
+ }
+
+ this.PredictIntraBlock(
+ partitionInfo,
+ plane,
+ transformSize,
+ tileInfo,
+ ref pixelRef,
+ stride,
+ ref topNeighbor,
+ ref leftNeighbor,
+ stride,
+ mode,
+ blockModeInfoColumnOffset,
+ blockModeInfoRowOffset,
+ bitDepth);
+ }
+
+ private void PredictChromaFromLumaBlock(Av1PartitionInfo partitionInfo, Av1ChromaFromLumaContext? chromaFromLumaContext, ref Span pixelBuffer, int stride, Av1TransformSize transformSize, Av1Plane plane)
+ {
+ Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo;
+ bool isChromaFromLumaAllowedFlag = IsChromaFromLumaAllowedWithFrameHeader(partitionInfo, this.sequenceHeader.ColorConfig, this.frameHeader);
+ DebugGuard.IsTrue(isChromaFromLumaAllowedFlag, "Chroma from Luma should be allowed then computing it.");
+
+ if (chromaFromLumaContext == null)
+ {
+ throw new InvalidOperationException("CFL context should have been defined already.");
+ }
+
+ if (!chromaFromLumaContext.AreParametersComputed)
+ {
+ chromaFromLumaContext.ComputeParameters(transformSize);
+ }
+
+ int alphaQ3 = ChromaFromLumaIndexToAlpha(modeInfo.ChromaFromLumaAlphaIndex, modeInfo.ChromaFromLumaAlphaSign, (Av1Plane)((int)plane - 1));
+
+ // assert((transformSize.GetHeight() - 1) * CFL_BUF_LINE + transformSize.GetWidth() <= CFL_BUF_SQUARE);
+ Av1BitDepth bitDepth = this.sequenceHeader.ColorConfig.BitDepth;
+ if ((bitDepth != Av1BitDepth.EightBit) || this.is16BitPipeline)
+ {
+ /* 16 bit pipeline
+ svt_cfl_predict_hbd(
+ chromaFromLumaContext->recon_buf_q3,
+ (uint16_t*)dst,
+ dst_stride,
+ (uint16_t*)dst,
+ dst_stride,
+ alpha_q3,
+ cc->bit_depth,
+ tx_size_wide[tx_size],
+ tx_size_high[tx_size]);
+ return;*/
+ }
+
+ ChromaFromLumaPredict(
+ chromaFromLumaContext.Q3Buffer!.DangerousGetSingleSpan(),
+ pixelBuffer,
+ stride,
+ pixelBuffer,
+ stride,
+ alphaQ3,
+ bitDepth,
+ transformSize.GetWidth(),
+ transformSize.GetHeight());
+ }
+
+ private static bool IsChromaFromLumaAllowedWithFrameHeader(Av1PartitionInfo partitionInfo, ObuColorConfig colorConfig, ObuFrameHeader frameHeader)
+ {
+ Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo;
+ Av1BlockSize blockSize = modeInfo.BlockSize;
+ DebugGuard.MustBeGreaterThan((int)blockSize, (int)Av1BlockSize.AllSizes, nameof(blockSize));
+ if (frameHeader.LosslessArray[modeInfo.SegmentId])
+ {
+ // In lossless, CfL is available when the partition size is equal to the
+ // transform size.
+ bool subX = colorConfig.SubSamplingX;
+ bool subY = colorConfig.SubSamplingY;
+ Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY);
+ return planeBlockSize == Av1BlockSize.Block4x4;
+ }
+
+ // Spec: CfL is available to luma partitions lesser than or equal to 32x32
+ return blockSize.GetWidth() <= 32 && blockSize.GetHeight() <= 32;
+ }
+
+ private static int ChromaFromLumaIndexToAlpha(int alphaIndex, int jointSign, Av1Plane plane)
+ {
+ int alphaSign = (plane == Av1Plane.U) ? Av1ChromaFromLumaMath.SignU(jointSign) : Av1ChromaFromLumaMath.SignV(jointSign);
+ if (alphaSign == Av1ChromaFromLumaMath.SignZero)
+ {
+ return 0;
+ }
+
+ int absAlphaQ3 = (plane == Av1Plane.U) ? Av1ChromaFromLumaMath.IndexU(alphaIndex) : Av1ChromaFromLumaMath.IndexV(alphaIndex);
+ return (alphaSign == Av1ChromaFromLumaMath.SignPositive) ? absAlphaQ3 + 1 : -absAlphaQ3 - 1;
+ }
+
+ private static int GetScaledLumaQ0(int alphaQ3, short predictedQ3)
+ {
+ int scaledLumaQ6 = alphaQ3 * predictedQ3;
+ return Av1Math.RoundPowerOf2Signed(scaledLumaQ6, 6);
+ }
+
+ private static void ChromaFromLumaPredict(Span predictedBufferQ3, Span predictedBuffer, int predictedStride, Span destinationBuffer, int destinationStride, int alphaQ3, Av1BitDepth bitDepth, int width, int height)
+ {
+ // TODO: Make SIMD variant of this method.
+ int maxPixelValue = (1 << bitDepth.GetBitCount()) - 1;
+ for (int j = 0; j < height; j++)
+ {
+ for (int i = 0; i < width; i++)
+ {
+ int alphaQ0 = GetScaledLumaQ0(alphaQ3, predictedBufferQ3[i]);
+ destinationBuffer[i] = (byte)Av1Math.Clamp(alphaQ0 + predictedBuffer[i], 0, maxPixelValue);
+ }
+
+ destinationBuffer = destinationBuffer[width..];
+ predictedBuffer = predictedBuffer[width..];
+ predictedBufferQ3 = predictedBufferQ3[width..];
+ }
+ }
+
+ private void PredictIntraBlock(
+ Av1PartitionInfo partitionInfo,
+ Av1Plane plane,
+ Av1TransformSize transformSize,
+ Av1TileInfo tileInfo,
+ ref byte pixelBuffer,
+ int pixelBufferStride,
+ ref byte topNeighbor,
+ ref byte leftNeighbor,
+ int referenceStride,
+ Av1PredictionMode mode,
+ int blockModeInfoColumnOffset,
+ int blockModeInfoRowOffset,
+ Av1BitDepth bitDepth)
+ {
+ // TODO:are_parameters_computed variable for CFL so that cal part for V plane we can skip,
+ // once we compute for U plane, this parameter is block level parameter.
+ ObuColorConfig cc = this.sequenceHeader.ColorConfig;
+ int subX = plane != Av1Plane.Y ? cc.SubSamplingX ? 1 : 0 : 0;
+ int subY = plane != Av1Plane.Y ? cc.SubSamplingY ? 1 : 0 : 0;
+
+ Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo;
+
+ int transformWidth = transformSize.GetWidth();
+ int transformHeight = transformSize.GetHeight();
+
+ bool usePalette = modeInfo.GetPaletteSize(plane) > 0;
+
+ if (usePalette)
+ {
+ return;
+ }
+
+ Av1FilterIntraMode filterIntraMode = (plane == Av1Plane.Y && modeInfo.FilterIntraModeInfo.UseFilterIntra)
+ ? modeInfo.FilterIntraModeInfo.Mode : Av1FilterIntraMode.FilterIntraModes;
+
+ int angleDelta = modeInfo.AngleDelta[Math.Min(1, (int)plane)];
+
+ Av1BlockSize blockSize = modeInfo.BlockSize;
+ bool haveTop = blockModeInfoRowOffset > 0 || (subY > 0 ? partitionInfo.AvailableAboveForChroma : partitionInfo.AvailableAbove);
+ bool haveLeft = blockModeInfoColumnOffset > 0 || (subX > 0 ? partitionInfo.AvailableLeftForChroma : partitionInfo.AvailableLeft);
+
+ int modeInfoRow = -partitionInfo.ModeBlockToTopEdge >> (3 + Av1Constants.ModeInfoSizeLog2);
+ int modeInfoColumn = -partitionInfo.ModeBlockToLeftEdge >> (3 + Av1Constants.ModeInfoSizeLog2);
+ int xrOffset = 0;
+ int ydOffset = 0;
+
+ // Distance between right edge of this pred block to frame right edge
+ int xr = (partitionInfo.ModeBlockToRightEdge >> (3 + subX)) + (partitionInfo.WidthInPixels[(int)plane] - (blockModeInfoColumnOffset << Av1Constants.ModeInfoSizeLog2) - transformWidth) -
+ xrOffset;
+
+ // Distance between bottom edge of this pred block to frame bottom edge
+ int yd = (partitionInfo.ModeBlockToBottomEdge >> (3 + subY)) +
+ (partitionInfo.HeightInPixels[(int)plane] - (blockModeInfoRowOffset << Av1Constants.ModeInfoSizeLog2) - transformHeight) - ydOffset;
+ bool rightAvailable = modeInfoColumn + ((blockModeInfoColumnOffset + transformWidth) << subX) < tileInfo.ModeInfoColumnEnd;
+ bool bottomAvailable = (yd > 0) && (modeInfoRow + ((blockModeInfoRowOffset + transformHeight) << subY) < tileInfo.ModeInfoRowEnd);
+
+ Av1PartitionType partition = modeInfo.PartitionType;
+
+ // force 4x4 chroma component block size.
+ blockSize = ScaleChromaBlockSize(blockSize, subX == 1, subY == 1);
+
+ bool haveTopRight = IntraHasTopRight(
+ this.sequenceHeader.SuperblockSize,
+ blockSize,
+ modeInfoRow,
+ modeInfoColumn,
+ haveTop,
+ rightAvailable,
+ partition,
+ transformSize,
+ blockModeInfoRowOffset,
+ blockModeInfoColumnOffset,
+ subX,
+ subY);
+ bool haveBottomLeft = IntraHasBottomLeft(
+ this.sequenceHeader.SuperblockSize,
+ blockSize,
+ modeInfoRow,
+ modeInfoColumn,
+ bottomAvailable,
+ haveLeft,
+ partition,
+ transformSize,
+ blockModeInfoRowOffset,
+ blockModeInfoColumnOffset,
+ subX,
+ subY);
+
+ bool disableEdgeFilter = !this.sequenceHeader.EnableIntraEdgeFilter;
+
+ // Calling all other intra predictors except CFL & pallate...
+ if (bitDepth == Av1BitDepth.EightBit && !this.is16BitPipeline)
+ {
+ this.DecodeBuildIntraPredictors(
+ partitionInfo,
+ ref topNeighbor,
+ ref leftNeighbor,
+ (nuint)referenceStride,
+ ref pixelBuffer,
+ (nuint)pixelBufferStride,
+ mode,
+ angleDelta,
+ filterIntraMode,
+ transformSize,
+ disableEdgeFilter,
+ haveTop ? Math.Min(transformWidth, xr + transformWidth) : 0,
+ haveTopRight ? Math.Min(transformWidth, xr) : 0,
+ haveLeft ? Math.Min(transformHeight, yd + transformHeight) : 0,
+ haveBottomLeft ? Math.Min(transformHeight, yd) : 0,
+ plane);
+ }
+ else
+ {
+ /* 16bit
+ decode_build_intra_predictors_high(xd,
+ (uint16_t*) top_neigh_array, //As per SVT Enc
+ (uint16_t*) left_neigh_array,
+ ref_stride,// As per SVT Enc
+ (uint16_t*) pv_pred_buf,
+ pred_stride,
+ mode,
+ angle_delta,
+ filter_intra_mode,
+ tx_size,
+ disable_edge_filter,
+ have_top? AOMMIN(transformWidth, xr + transformWidth) : 0,
+ have_top_right? AOMMIN(transformWidth, xr) : 0,
+ have_left? AOMMIN(transformHeight, yd + transformHeight) : 0,
+ have_bottom_left? AOMMIN(transformHeight, yd) : 0,
+ plane,
+ bit_depth);
+ */
+ }
+ }
+
+ private static Av1BlockSize ScaleChromaBlockSize(Av1BlockSize blockSize, bool subX, bool subY)
+ {
+ Av1BlockSize bs = blockSize;
+ switch (blockSize)
+ {
+ case Av1BlockSize.Block4x4:
+ if (subX && subY)
+ {
+ bs = Av1BlockSize.Block8x8;
+ }
+ else if (subX)
+ {
+ bs = Av1BlockSize.Block8x4;
+ }
+ else if (subY)
+ {
+ bs = Av1BlockSize.Block4x8;
+ }
+
+ break;
+ case Av1BlockSize.Block4x8:
+ if (subX && subY)
+ {
+ bs = Av1BlockSize.Block8x8;
+ }
+ else if (subX)
+ {
+ bs = Av1BlockSize.Block8x8;
+ }
+ else if (subY)
+ {
+ bs = Av1BlockSize.Block4x8;
+ }
+
+ break;
+ case Av1BlockSize.Block8x4:
+ if (subX && subY)
+ {
+ bs = Av1BlockSize.Block8x8;
+ }
+ else if (subX)
+ {
+ bs = Av1BlockSize.Block8x4;
+ }
+ else if (subY)
+ {
+ bs = Av1BlockSize.Block8x8;
+ }
+
+ break;
+ case Av1BlockSize.Block4x16:
+ if (subX && subY)
+ {
+ bs = Av1BlockSize.Block8x16;
+ }
+ else if (subX)
+ {
+ bs = Av1BlockSize.Block8x16;
+ }
+ else if (subY)
+ {
+ bs = Av1BlockSize.Block4x16;
+ }
+
+ break;
+ case Av1BlockSize.Block16x4:
+ if (subX && subY)
+ {
+ bs = Av1BlockSize.Block16x8;
+ }
+ else if (subX)
+ {
+ bs = Av1BlockSize.Block16x4;
+ }
+ else if (subY)
+ {
+ bs = Av1BlockSize.Block16x8;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ return bs;
+ }
+
+ private static bool IntraHasBottomLeft(Av1BlockSize superblockSize, Av1BlockSize blockSize, int modeInfoRow, int modeInfoColumn, bool bottomAvailable, bool haveLeft, Av1PartitionType partition, Av1TransformSize transformSize, int blockModeInfoRowOffset, int blockModeInfoColumnOffset, int subX, int subY)
+ {
+ if (!bottomAvailable || !haveLeft)
+ {
+ return false;
+ }
+
+ // Special case for 128x* blocks, when col_off is half the block width.
+ // This is needed because 128x* superblocks are divided into 64x* blocks in
+ // raster order
+ if (blockSize.GetWidth() > 64 && blockModeInfoColumnOffset > 0)
+ {
+ int planeBlockWidthInUnits64 = 64 >> subX;
+ int columnOffset64 = blockModeInfoColumnOffset % planeBlockWidthInUnits64;
+ if (columnOffset64 == 0)
+ {
+ // We are at the left edge of top-right or bottom-right 64x* block.
+ int planeBlockHeightInUnits64 = 64 >> subY;
+ int rowOffset64 = blockModeInfoRowOffset % planeBlockHeightInUnits64;
+ int planeBlockHeightInUnits = Math.Min(blockSize.Get4x4HighCount() >> subY, planeBlockHeightInUnits64);
+
+ // Check if all bottom-left pixels are in the left 64x* block (which is
+ // already coded).
+ return rowOffset64 + transformSize.Get4x4HighCount() < planeBlockHeightInUnits;
+ }
+ }
+
+ if (blockModeInfoColumnOffset > 0)
+ {
+ // Bottom-left pixels are in the bottom-left block, which is not available.
+ return false;
+ }
+ else
+ {
+ int blockHeightInUnits = blockSize.GetHeight() >> Av1TransformSize.Size4x4.GetBlockHeightLog2();
+ int planeBlockHeightInUnits = Math.Max(blockHeightInUnits >> subY, 1);
+ int bottomLeftUnitCount = transformSize.Get4x4HighCount();
+
+ // All bottom-left pixels are in the left block, which is already available.
+ if (blockModeInfoRowOffset + bottomLeftUnitCount < planeBlockHeightInUnits)
+ {
+ return true;
+ }
+
+ int blockWidthInModeInfoLog2 = blockSize.Get4x4WidthLog2();
+ int blockHeightInModeInfoLog2 = blockSize.Get4x4HeightLog2();
+ int superblockModeInfoSize = superblockSize.Get4x4HighCount();
+ int blockRowInSuperblock = (modeInfoRow & (superblockModeInfoSize - 1)) >> blockHeightInModeInfoLog2;
+ int blockColumnInSuperblock = (modeInfoColumn & (superblockModeInfoSize - 1)) >> blockWidthInModeInfoLog2;
+
+ // Leftmost column of superblock: so bottom-left pixels maybe in the left
+ // and/or bottom-left superblocks. But only the left superblock is
+ // available, so check if all required pixels fall in that superblock.
+ if (blockColumnInSuperblock == 0)
+ {
+ int blockStartRowOffset = blockRowInSuperblock << (blockHeightInModeInfoLog2 + Av1Constants.ModeInfoSizeLog2 - Av1TransformSize.Size4x4.GetBlockWidthLog2()) >> subY;
+ int rowOffsetInSuperblock = blockStartRowOffset + blockModeInfoRowOffset;
+ int superblockHeightInUnits = superblockModeInfoSize >> subY;
+ return rowOffsetInSuperblock + bottomLeftUnitCount < superblockHeightInUnits;
+ }
+
+ // Bottom row of superblock (and not the leftmost column): so bottom-left
+ // pixels fall in the bottom superblock, which is not available yet.
+ if (((blockRowInSuperblock + 1) << blockHeightInModeInfoLog2) >= superblockModeInfoSize)
+ {
+ return false;
+ }
+
+ // General case (neither leftmost column nor bottom row): check if the
+ // bottom-left block is coded before the current block.
+ int thisBlockIndex = ((blockRowInSuperblock + 0) << (Av1Constants.MaxSuperBlockSizeLog2 - Av1Constants.ModeInfoSizeLog2 - blockWidthInModeInfoLog2)) + blockColumnInSuperblock + 0;
+ return Av1BottomRightTopLeftConstants.HasBottomLeft(partition, blockSize, thisBlockIndex);
+ }
+ }
+
+ private static bool IntraHasTopRight(Av1BlockSize superblockSize, Av1BlockSize blockSize, int modeInfoRow, int modeInfoColumn, bool haveTop, bool rightAvailable, Av1PartitionType partition, Av1TransformSize transformSize, int blockModeInfoRowOffset, int blockModeInfoColumnOffset, int subX, int subY)
+ {
+ if (!haveTop || !rightAvailable)
+ {
+ return false;
+ }
+
+ int blockWideInUnits = blockSize.GetWidth() >> 2;
+ int planeBlockWidthInUnits = Math.Max(blockWideInUnits >> subX, 1);
+ int topRightUnitCount = transformSize.Get4x4WideCount();
+
+ if (blockModeInfoRowOffset > 0)
+ { // Just need to check if enough pixels on the right.
+ if (blockSize.GetWidth() > 64)
+ {
+ // Special case: For 128x128 blocks, the transform unit whose
+ // top-right corner is at the center of the block does in fact have
+ // pixels available at its top-right corner.
+ if (blockModeInfoRowOffset == 64 >> subY &&
+ blockModeInfoColumnOffset + topRightUnitCount == 64 >> subX)
+ {
+ return true;
+ }
+
+ int planeBlockWidthInUnits64 = 64 >> subX;
+ int blockModeInfoColumnOffset64 = blockModeInfoColumnOffset % planeBlockWidthInUnits64;
+ return blockModeInfoColumnOffset64 + topRightUnitCount < planeBlockWidthInUnits64;
+ }
+
+ return blockModeInfoColumnOffset + topRightUnitCount < planeBlockWidthInUnits;
+ }
+ else
+ {
+ // All top-right pixels are in the block above, which is already available.
+ if (blockModeInfoColumnOffset + topRightUnitCount < planeBlockWidthInUnits)
+ {
+ return true;
+ }
+
+ int blockWidthInModeInfoLog2 = blockSize.Get4x4WidthLog2();
+ int blockHeightInModeInfeLog2 = blockSize.Get4x4HeightLog2();
+ int superBlockModeInfoSize = superblockSize.Get4x4HighCount();
+ int blockRowInSuperblock = (modeInfoRow & (superBlockModeInfoSize - 1)) >> blockHeightInModeInfeLog2;
+ int blockColumnInSuperBlock = (modeInfoColumn & (superBlockModeInfoSize - 1)) >> blockWidthInModeInfoLog2;
+
+ // Top row of superblock: so top-right pixels are in the top and/or
+ // top-right superblocks, both of which are already available.
+ if (blockRowInSuperblock == 0)
+ {
+ return true;
+ }
+
+ // Rightmost column of superblock (and not the top row): so top-right pixels
+ // fall in the right superblock, which is not available yet.
+ if (((blockColumnInSuperBlock + 1) << blockWidthInModeInfoLog2) >= superBlockModeInfoSize)
+ {
+ return false;
+ }
+
+ // General case (neither top row nor rightmost column): check if the
+ // top-right block is coded before the current block.
+ int thisBlockIndex = ((blockRowInSuperblock + 0) << (Av1Constants.MaxSuperBlockSizeLog2 - Av1Constants.ModeInfoSizeLog2 - blockWidthInModeInfoLog2)) + blockColumnInSuperBlock + 0;
+ return Av1BottomRightTopLeftConstants.HasTopRight(partition, blockSize, thisBlockIndex);
+ }
+ }
+
+ private void DecodeBuildIntraPredictors(
+ Av1PartitionInfo partitionInfo,
+ ref byte aboveNeighbor,
+ ref byte leftNeighbor,
+ nuint referenceStride,
+ ref byte destination,
+ nuint destinationStride,
+ Av1PredictionMode mode,
+ int angleDelta,
+ Av1FilterIntraMode filterIntraMode,
+ Av1TransformSize transformSize,
+ bool disableEdgeFilter,
+ int topPixelCount,
+ int topRightPixelCount,
+ int leftPixelCount,
+ int bottomLeftPixelCount,
+ Av1Plane plane)
+ {
+ Span aboveData = stackalloc byte[(Av1Constants.MaxTransformSize * 2) + 32];
+ Span leftData = stackalloc byte[(Av1Constants.MaxTransformSize * 2) + 32];
+ Span aboveRow = aboveData[16..];
+ Span leftColumn = leftData[16..];
+ int transformWidth = transformSize.GetWidth();
+ int transformHeight = transformSize.GetHeight();
+ bool isDirectionalMode = mode.IsDirectional();
+ Av1NeighborNeed need = mode.GetNeighborNeed();
+ bool needLeft = (need & Av1NeighborNeed.Left) == Av1NeighborNeed.Left;
+ bool needAbove = (need & Av1NeighborNeed.Above) == Av1NeighborNeed.Above;
+ bool needAboveLeft = (need & Av1NeighborNeed.AboveLeft) == Av1NeighborNeed.AboveLeft;
+ int angle = 0;
+ bool useFilterIntra = filterIntraMode != Av1FilterIntraMode.FilterIntraModes;
+
+ if (isDirectionalMode)
+ {
+ angle = mode.ToAngle() + (angleDelta * Av1Constants.AngleStep);
+ if (angle <= 90)
+ {
+ needAbove = true;
+ needLeft = false;
+ needAboveLeft = true;
+ }
+ else if (angle < 180)
+ {
+ needAbove = true;
+ needLeft = true;
+ needAboveLeft = true;
+ }
+ else
+ {
+ needAbove = false;
+ needLeft = true;
+ needAboveLeft = true;
+ }
+ }
+
+ if (useFilterIntra)
+ {
+ needAbove = true;
+ needLeft = true;
+ needAboveLeft = true;
+ }
+
+ DebugGuard.MustBeGreaterThanOrEqualTo(topPixelCount, 0, nameof(topPixelCount));
+ DebugGuard.MustBeGreaterThanOrEqualTo(topRightPixelCount, 0, nameof(topRightPixelCount));
+ DebugGuard.MustBeGreaterThanOrEqualTo(leftPixelCount, 0, nameof(leftPixelCount));
+ DebugGuard.MustBeGreaterThanOrEqualTo(bottomLeftPixelCount, 0, nameof(bottomLeftPixelCount));
+
+ if ((!needAbove && leftPixelCount == 0) || (!needLeft && topPixelCount == 0))
+ {
+ byte val;
+ if (needLeft)
+ {
+ val = (byte)((topPixelCount > 0) ? aboveNeighbor : 129);
+ }
+ else
+ {
+ val = (byte)((leftPixelCount > 0) ? leftNeighbor : 127);
+ }
+
+ for (int i = 0; i < transformHeight; ++i)
+ {
+ Unsafe.InitBlock(ref destination, val, (uint)transformWidth);
+ destination = ref Unsafe.Add(ref destination, destinationStride);
+ }
+
+ return;
+ }
+
+ // NEED_LEFT
+ if (needLeft)
+ {
+ bool needBottom = (need & Av1NeighborNeed.BottomLeft) == Av1NeighborNeed.BottomLeft;
+ if (useFilterIntra)
+ {
+ needBottom = false;
+ }
+
+ if (isDirectionalMode)
+ {
+ needBottom = angle > 180;
+ }
+
+ uint numLeftPixelsNeeded = (uint)(transformHeight + (needBottom ? transformWidth : 0));
+ int i = 0;
+ if (leftPixelCount > 0)
+ {
+ for (; i < leftPixelCount; i++)
+ {
+ leftColumn[i] = Unsafe.Add(ref leftNeighbor, i * (int)referenceStride);
+ }
+
+ if (needBottom && bottomLeftPixelCount > 0)
+ {
+ Guard.IsTrue(i == transformHeight, nameof(i), string.Empty);
+ for (; i < transformHeight + bottomLeftPixelCount; i++)
+ {
+ leftColumn[i] = Unsafe.Add(ref leftNeighbor, i * (int)referenceStride);
+ }
+ }
+
+ if (i < numLeftPixelsNeeded)
+ {
+ Unsafe.InitBlock(ref leftColumn[i], leftColumn[i - 1], numLeftPixelsNeeded - (uint)i);
+ }
+ }
+ else
+ {
+ if (topPixelCount > 0)
+ {
+ Unsafe.InitBlock(ref leftColumn[0], aboveNeighbor, numLeftPixelsNeeded);
+ }
+ else
+ {
+ Unsafe.InitBlock(ref leftColumn[0], 129, numLeftPixelsNeeded);
+ }
+ }
+ }
+
+ // NEED_ABOVE
+ if (needAbove)
+ {
+ bool needRight = (need & Av1NeighborNeed.AboveRight) == Av1NeighborNeed.AboveRight;
+ if (useFilterIntra)
+ {
+ needRight = false;
+ }
+
+ if (isDirectionalMode)
+ {
+ needRight = angle < 90;
+ }
+
+ uint numTopPixelsNeeded = (uint)(transformWidth + (needRight ? transformHeight : 0));
+ if (topPixelCount > 0)
+ {
+ Unsafe.CopyBlock(ref aboveRow[0], ref aboveNeighbor, (uint)topPixelCount);
+ int i = topPixelCount;
+ if (needRight && topPixelCount > 0)
+ {
+ Guard.IsTrue(topPixelCount == transformWidth, nameof(topPixelCount), string.Empty);
+ Unsafe.CopyBlock(ref aboveRow[transformWidth], ref Unsafe.Add(ref aboveNeighbor, transformWidth), (uint)topPixelCount);
+ i += topPixelCount;
+ }
+
+ if (i < numTopPixelsNeeded)
+ {
+ Unsafe.InitBlock(ref aboveRow[i], aboveRow[i - 1], numTopPixelsNeeded - (uint)i);
+ }
+ }
+ else
+ {
+ if (leftPixelCount > 0)
+ {
+ Unsafe.InitBlock(ref aboveRow[0], leftNeighbor, numTopPixelsNeeded);
+ }
+ else
+ {
+ Unsafe.InitBlock(ref aboveRow[0], 127, numTopPixelsNeeded);
+ }
+ }
+ }
+
+ if (needAboveLeft)
+ {
+ if (topPixelCount > 0 && leftPixelCount > 0)
+ {
+ aboveRow[-1] = Unsafe.Subtract(ref aboveNeighbor, 1);
+ }
+ else if (topPixelCount > 0)
+ {
+ aboveRow[-1] = aboveNeighbor;
+ }
+ else if (leftPixelCount > 0)
+ {
+ aboveRow[-1] = leftNeighbor;
+ }
+ else
+ {
+ aboveRow[-1] = 128;
+ }
+
+ leftColumn[-1] = aboveRow[-1];
+ }
+
+ if (useFilterIntra)
+ {
+ Av1PredictorFactory.FilterIntraPredictor(ref destination, destinationStride, transformSize, aboveRow, leftColumn, filterIntraMode);
+ return;
+ }
+
+ if (isDirectionalMode)
+ {
+ bool upsampleAbove = false;
+ bool upsampleLeft = false;
+ if (!disableEdgeFilter)
+ {
+ bool needRight = angle < 90;
+ bool needBottom = angle > 180;
+
+ bool filterType = GetFilterType(partitionInfo, plane);
+
+ if (angle is not 90 and not 180)
+ {
+ int ab_le = needAboveLeft ? 1 : 0;
+ if (needAbove && needLeft && (transformWidth + transformHeight >= 24))
+ {
+ FilterIntraEdgeCorner(aboveRow, leftColumn);
+ }
+
+ if (needAbove && topPixelCount > 0)
+ {
+ int strength = IntraEdgeFilterStrength(transformWidth, transformHeight, angle - 90, filterType);
+ int pixelCount = topPixelCount + ab_le + (needRight ? transformHeight : 0);
+ FilterIntraEdge(ref Unsafe.Subtract(ref aboveRow[0], ab_le), pixelCount, strength);
+ }
+
+ if (needLeft && leftPixelCount > 0)
+ {
+ int strength = IntraEdgeFilterStrength(transformHeight, transformWidth, angle - 180, filterType);
+ int pixelCount = leftPixelCount + ab_le + (needBottom ? transformWidth : 0);
+ FilterIntraEdge(ref Unsafe.Subtract(ref leftColumn[0], ab_le), pixelCount, strength);
+ }
+ }
+
+ upsampleAbove = UseIntraEdgeUpsample(transformWidth, transformHeight, angle - 90, filterType);
+ if (needAbove && upsampleAbove)
+ {
+ int pixelCount = transformWidth + (needRight ? transformHeight : 0);
+
+ UpsampleIntraEdge(aboveRow, pixelCount);
+ }
+
+ upsampleLeft = UseIntraEdgeUpsample(transformHeight, transformWidth, angle - 180, filterType);
+ if (needLeft && upsampleLeft)
+ {
+ int pixelCount = transformHeight + (needBottom ? transformWidth : 0);
+
+ UpsampleIntraEdge(leftColumn, pixelCount);
+ }
+ }
+
+ Av1PredictorFactory.DirectionalPredictor(ref destination, destinationStride, transformSize, aboveRow, leftColumn, upsampleAbove, upsampleLeft, angle);
+ return;
+ }
+
+ // predict
+ if (mode == Av1PredictionMode.DC)
+ {
+ Av1PredictorFactory.DcPredictor(leftPixelCount > 0, topPixelCount > 0, transformSize, ref destination, destinationStride, aboveRow, leftColumn);
+ }
+ else
+ {
+ Av1PredictorFactory.GeneralPredictor(mode, transformSize, ref destination, destinationStride, aboveRow, leftColumn);
+ }
+ }
+
+ private static void UpsampleIntraEdge(Span buffer, int count)
+ {
+ // TODO: Consider creating SIMD version
+
+ // interpolate half-sample positions
+ Guard.MustBeLessThanOrEqualTo(count, MaxUpsampleSize, nameof(count));
+
+ Span input = stackalloc byte[MaxUpsampleSize + 3];
+ byte beforeBuffer = Unsafe.Subtract(ref buffer[0], 1);
+
+ // copy p[-1..(sz-1)] and extend first and last samples
+ input[0] = beforeBuffer;
+ input[1] = beforeBuffer;
+ for (int i = 0; i < count; i++)
+ {
+ input[i + 2] = buffer[i];
+ }
+
+ input[count + 2] = buffer[count - 1];
+
+ // interpolate half-sample edge positions
+ buffer[-2] = input[0];
+ for (int i = 0; i < count; i++)
+ {
+ int s = -input[i] + (9 * input[i + 1]) + (9 * input[i + 2]) - input[i + 3];
+ s = Av1Math.Clamp((s + 8) >> 4, 0, 255);
+ buffer[(2 * i) - 1] = (byte)s;
+ buffer[2 * i] = input[i + 2];
+ }
+ }
+
+ private static bool UseIntraEdgeUpsample(int width, int height, int delta, bool type)
+ {
+ int d = Math.Abs(delta);
+ int widthHeight = width + height;
+ if (d is <= 0 or >= 40)
+ {
+ return false;
+ }
+
+ return type ? (widthHeight <= 8) : (widthHeight <= 16);
+ }
+
+ private static void FilterIntraEdge(ref byte buffer, int count, int strength)
+ {
+ // TODO: Consider creating SIMD version
+ if (strength == 0)
+ {
+ return;
+ }
+
+ int[][] kernel = [
+ [0, 4, 8, 4, 0], [0, 5, 6, 5, 0], [2, 4, 4, 4, 2]
+ ];
+ int filt = strength - 1;
+ Span edge = stackalloc byte[129];
+
+ Unsafe.CopyBlock(ref edge[0], ref buffer, (uint)count);
+ for (int i = 1; i < count; i++)
+ {
+ int s = 0;
+ for (int j = 0; j < 5; j++)
+ {
+ int k = i - 2 + j;
+ k = (k < 0) ? 0 : k;
+ k = (k > count - 1) ? count - 1 : k;
+ s += edge[k] * kernel[filt][j];
+ }
+
+ s = (s + 8) >> 4;
+ Unsafe.Add(ref buffer, i) = (byte)s;
+ }
+ }
+
+ private static int IntraEdgeFilterStrength(int width, int height, int delta, bool filterType)
+ {
+ int d = Math.Abs(delta);
+ int strength = 0;
+ int widthHeight = width + height;
+ if (!filterType)
+ {
+ if (widthHeight <= 8)
+ {
+ if (d >= 56)
+ {
+ strength = 1;
+ }
+ }
+ else if (widthHeight <= 12)
+ {
+ if (d >= 40)
+ {
+ strength = 1;
+ }
+ }
+ else if (widthHeight <= 16)
+ {
+ if (d >= 40)
+ {
+ strength = 1;
+ }
+ }
+ else if (widthHeight <= 24)
+ {
+ if (d >= 8)
+ {
+ strength = 1;
+ }
+
+ if (d >= 16)
+ {
+ strength = 2;
+ }
+
+ if (d >= 32)
+ {
+ strength = 3;
+ }
+ }
+ else if (widthHeight <= 32)
+ {
+ if (d >= 1)
+ {
+ strength = 1;
+ }
+
+ if (d >= 4)
+ {
+ strength = 2;
+ }
+
+ if (d >= 32)
+ {
+ strength = 3;
+ }
+ }
+ else
+ {
+ if (d >= 1)
+ {
+ strength = 3;
+ }
+ }
+ }
+ else
+ {
+ if (widthHeight <= 8)
+ {
+ if (d >= 40)
+ {
+ strength = 1;
+ }
+
+ if (d >= 64)
+ {
+ strength = 2;
+ }
+ }
+ else if (widthHeight <= 16)
+ {
+ if (d >= 20)
+ {
+ strength = 1;
+ }
+
+ if (d >= 48)
+ {
+ strength = 2;
+ }
+ }
+ else if (widthHeight <= 24)
+ {
+ if (d >= 4)
+ {
+ strength = 3;
+ }
+ }
+ else
+ {
+ if (d >= 1)
+ {
+ strength = 3;
+ }
+ }
+ }
+
+ return strength;
+ }
+
+ private static void FilterIntraEdgeCorner(Span above, Span left)
+ {
+ int[] kernel = [5, 6, 5];
+
+ ref byte aboveRef = ref above[0];
+ ref byte leftRef = ref left[0];
+ ref byte abovePreviousRef = ref Unsafe.Subtract(ref aboveRef, 1);
+ ref byte leftPreviousRef = ref Unsafe.Subtract(ref leftRef, 1);
+ int s = (leftRef * kernel[0]) + (abovePreviousRef * kernel[1]) + (aboveRef * kernel[2]);
+ s = (s + 8) >> 4;
+ abovePreviousRef = (byte)s;
+ leftPreviousRef = (byte)s;
+ }
+
+ private static bool GetFilterType(Av1PartitionInfo partitionInfo, Av1Plane plane)
+ {
+ Av1BlockModeInfo? above;
+ Av1BlockModeInfo? left;
+ if (plane == Av1Plane.Y)
+ {
+ above = partitionInfo.AboveModeInfo;
+ left = partitionInfo.LeftModeInfo;
+ }
+ else
+ {
+ above = partitionInfo.AboveModeInfoForChroma;
+ left = partitionInfo.LeftModeInfoForChroma;
+ }
+
+ bool aboveIsSmooth = (above != null) && IsSmooth(above, plane);
+ bool leftIsSmooth = (left != null) && IsSmooth(left, plane);
+ return aboveIsSmooth || leftIsSmooth;
+ }
+
+ private static bool IsSmooth(Av1BlockModeInfo modeInfo, Av1Plane plane)
+ {
+ if (plane == Av1Plane.Y)
+ {
+ Av1PredictionMode mode = modeInfo.YMode;
+ return mode is Av1PredictionMode.Smooth or
+ Av1PredictionMode.SmoothVertical or
+ Av1PredictionMode.SmoothHorizontal;
+ }
+ else
+ {
+ // Inter mode not supported here.
+ Av1PredictionMode uvMode = modeInfo.UvMode;
+ return uvMode is Av1PredictionMode.Smooth or
+ Av1PredictionMode.SmoothVertical or
+ Av1PredictionMode.SmoothHorizontal;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs
new file mode 100644
index 0000000000..dc35c96f31
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+// Inter modes are not defined here, as they do not apply to pictures.
+internal enum Av1PredictionMode
+{
+ DC,
+ Vertical,
+ Horizontal,
+ Directional45Degrees,
+ Directional135Degrees,
+ Directional113Degrees,
+ Directional157Degrees,
+ Directional203Degrees,
+ Directional67Degrees,
+ Smooth,
+ SmoothVertical,
+ SmoothHorizontal,
+ Paeth,
+ UvChromaFromLuma,
+ IntraModeStart = DC,
+ IntraModeEnd = Paeth + 1,
+ IntraModes = Paeth,
+ IntraInvalid = 25,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs
new file mode 100644
index 0000000000..12349cc010
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1PredictorFactory
+{
+ internal static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, ref byte destination, nuint destinationStride, Span aboveRow, Span leftColumn)
+ {
+ if (hasLeft)
+ {
+ if (hasAbove)
+ {
+ Av1DcPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]);
+ }
+ else
+ {
+ Av1DcLeftPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]);
+ }
+ }
+ else
+ {
+ if (hasAbove)
+ {
+ Av1DcTopPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]);
+ }
+ else
+ {
+ Av1DcFillPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]);
+ }
+ }
+ }
+
+ internal static void DirectionalPredictor(ref byte destination, nuint destinationStride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, bool upsampleAbove, bool upsampleLeft, int angle) => throw new NotImplementedException();
+
+ internal static void FilterIntraPredictor(ref byte destination, nuint destinationStride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, Av1FilterIntraMode filterIntraMode) => throw new NotImplementedException();
+
+ internal static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, ref byte destination, nuint destinationStride, Span aboveRow, Span leftColumn) => throw new NotImplementedException();
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs
new file mode 100644
index 0000000000..2b2ca3e4f3
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs
@@ -0,0 +1,67 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal static class Av1PreditionModeExtensions
+{
+ private static readonly Av1TransformType[] IntraPreditionMode2TransformType = [
+ Av1TransformType.DctDct, // DC
+ Av1TransformType.AdstDct, // V
+ Av1TransformType.DctAdst, // H
+ Av1TransformType.DctDct, // D45
+ Av1TransformType.AdstAdst, // D135
+ Av1TransformType.AdstDct, // D117
+ Av1TransformType.DctAdst, // D153
+ Av1TransformType.DctAdst, // D207
+ Av1TransformType.AdstDct, // D63
+ Av1TransformType.AdstAdst, // SMOOTH
+ Av1TransformType.AdstDct, // SMOOTH_V
+ Av1TransformType.DctAdst, // SMOOTH_H
+ Av1TransformType.AdstAdst, // PAETH
+ ];
+
+ private static readonly Av1NeighborNeed[] NeedsMap = [
+ Av1NeighborNeed.Above | Av1NeighborNeed.Left, // DC
+ Av1NeighborNeed.Above, // V
+ Av1NeighborNeed.Left, // H
+ Av1NeighborNeed.Above | Av1NeighborNeed.AboveRight, // D45
+ Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // D135
+ Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // D113
+ Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // D157
+ Av1NeighborNeed.Left | Av1NeighborNeed.BottomLeft, // D203
+ Av1NeighborNeed.Above | Av1NeighborNeed.AboveRight, // D67
+ Av1NeighborNeed.Left | Av1NeighborNeed.Above, // SMOOTH
+ Av1NeighborNeed.Left | Av1NeighborNeed.Above, // SMOOTH_V
+ Av1NeighborNeed.Left | Av1NeighborNeed.Above, // SMOOTH_H
+ Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // PAETH
+ ];
+
+ private static readonly int[] AngleMap = [
+ 0,
+ 90,
+ 180,
+ 45,
+ 135,
+ 113,
+ 157,
+ 203,
+ 67,
+ 0,
+ 0,
+ 0,
+ 0,
+ ];
+
+ public static Av1TransformType ToTransformType(this Av1PredictionMode mode) => IntraPreditionMode2TransformType[(int)mode];
+
+ public static bool IsDirectional(this Av1PredictionMode mode)
+ => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees;
+
+ public static Av1NeighborNeed GetNeighborNeed(this Av1PredictionMode mode) => NeedsMap[(int)mode];
+
+ public static int ToAngle(this Av1PredictionMode mode) => AngleMap[(int)mode];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs
new file mode 100644
index 0000000000..c8112028db
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs
@@ -0,0 +1,120 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
+
+internal class Av1ChromaFromLumaContext
+{
+ private const int BufferLine = 32;
+
+ private int bufferHeight;
+ private int bufferWidth;
+ private readonly bool subX;
+ private readonly bool subY;
+
+ public Av1ChromaFromLumaContext(Configuration configuration, ObuColorConfig colorConfig)
+ {
+ this.subX = colorConfig.SubSamplingX;
+ this.subY = colorConfig.SubSamplingY;
+ this.Q3Buffer = configuration.MemoryAllocator.Allocate2D(new Size(32, 32), AllocationOptions.Clean);
+ }
+
+ public Buffer2D Q3Buffer { get; private set; }
+
+ public bool AreParametersComputed { get; private set; }
+
+ public void ComputeParameters(Av1TransformSize transformSize)
+ {
+ Guard.IsFalse(this.AreParametersComputed, nameof(this.AreParametersComputed), "Do not call cfl_compute_parameters multiple time on the same values.");
+ this.Pad(transformSize.GetWidth(), transformSize.GetHeight());
+ SubtractAverage(ref this.Q3Buffer[0, 0], transformSize);
+ this.AreParametersComputed = true;
+ }
+
+ private void Pad(int width, int height)
+ {
+ int diff_width = width - this.bufferWidth;
+ int diff_height = height - this.bufferHeight;
+
+ if (diff_width > 0)
+ {
+ int min_height = height - diff_height;
+ ref short recon_buf_q3 = ref this.Q3Buffer[width - diff_width, 0];
+ for (int j = 0; j < min_height; j++)
+ {
+ short last_pixel = Unsafe.Subtract(ref recon_buf_q3, 1);
+ Guard.IsTrue(Unsafe.IsAddressLessThan(ref Unsafe.Add(ref recon_buf_q3, diff_width), ref this.Q3Buffer[BufferLine, BufferLine]), nameof(recon_buf_q3), "Shall stay within bounds.");
+ for (int i = 0; i < diff_width; i++)
+ {
+ Unsafe.Add(ref recon_buf_q3, i) = last_pixel;
+ }
+
+ recon_buf_q3 += BufferLine;
+ }
+
+ this.bufferWidth = width;
+ }
+
+ if (diff_height > 0)
+ {
+ ref short recon_buf_q3 = ref this.Q3Buffer[0, height - diff_height];
+ for (int j = 0; j < diff_height; j++)
+ {
+ ref short last_row_q3 = ref Unsafe.Subtract(ref recon_buf_q3, BufferLine);
+ Guard.IsTrue(Unsafe.IsAddressLessThan(ref Unsafe.Add(ref recon_buf_q3, diff_width), ref this.Q3Buffer[BufferLine, BufferLine]), nameof(recon_buf_q3), "Shall stay within bounds.");
+ for (int i = 0; i < width; i++)
+ {
+ Unsafe.Add(ref recon_buf_q3, i) = Unsafe.Add(ref last_row_q3, i);
+ }
+
+ recon_buf_q3 += BufferLine;
+ }
+
+ this.bufferHeight = height;
+ }
+ }
+
+ /************************************************************************************************
+ * svt_subtract_average_c
+ * Calculate the DC value by averaging over all sample. Subtract DC value to get AC values In C
+ ************************************************************************************************/
+ private static void SubtractAverage(ref short pred_buf_q3, Av1TransformSize transformSize)
+ {
+ int width = transformSize.GetWidth();
+ int height = transformSize.GetHeight();
+ int roundOffset = (width * height) >> 1;
+ int pelCountLog2 = transformSize.GetBlockWidthLog2() + transformSize.GetBlockHeightLog2();
+ int sum_q3 = 0;
+ ref short pred_buf = ref pred_buf_q3;
+ for (int j = 0; j < height; j++)
+ {
+ // assert(pred_buf_q3 + tx_width <= cfl->pred_buf_q3 + CFL_BUF_SQUARE);
+ for (int i = 0; i < width; i++)
+ {
+ sum_q3 += Unsafe.Add(ref pred_buf, i);
+ }
+
+ pred_buf += BufferLine;
+ }
+
+ int avg_q3 = (sum_q3 + roundOffset) >> pelCountLog2;
+
+ // Loss is never more than 1/2 (in Q3)
+ // assert(abs((avg_q3 * (1 << num_pel_log2)) - sum_q3) <= 1 << num_pel_log2 >>
+ // 1);
+ for (int j = 0; j < height; j++)
+ {
+ for (int i = 0; i < width; i++)
+ {
+ Unsafe.Add(ref pred_buf_q3, i) -= (short)avg_q3;
+ }
+
+ pred_buf_q3 += BufferLine;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs
new file mode 100644
index 0000000000..8f3fce0164
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
+
+internal static class Av1ChromaFromLumaMath
+{
+ private const int Signs = 3;
+ private const int AlphabetSizeLog2 = 4;
+
+ public const int SignZero = 0;
+ public const int SignNegative = 1;
+ public const int SignPositive = 2;
+
+ public static int SignU(int jointSign) => ((jointSign + 1) * 11) >> 5;
+
+ public static int SignV(int jointSign) => (jointSign + 1) - (Signs * SignU(jointSign));
+
+ public static int IndexU(int index) => index >> AlphabetSizeLog2;
+
+ public static int IndexV(int index) => index & (AlphabetSizeLog2 - 1);
+
+ public static int ContextU(int jointSign) => jointSign + 1 - Signs;
+
+ public static int ContextV(int jointSign) => (SignV(jointSign) * Signs) + SignU(jointSign) - Signs;
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs
new file mode 100644
index 0000000000..3bf8907789
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+///
+/// Interface for predictor implementations.
+///
+internal interface IAv1Predictor
+{
+ ///
+ /// Predict using scalar logic within the 8-bit pipeline.
+ ///
+ /// The destination to write to.
+ /// The stride of the destination buffer.
+ /// Pointer to the first element of the block above.
+ /// Pointer to the first element of the block to the left.
+ public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left);
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Readme.md b/src/ImageSharp/Formats/Heif/Av1/Readme.md
new file mode 100644
index 0000000000..9f658e90c8
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Readme.md
@@ -0,0 +1,73 @@
+# Open Bitstream Unit
+
+An OBU unit is a unit of parameters encoded in a bitstream format. In AVIF, it contains a single frame.
+This frame is coded using no other frame as reference, it is a so called INTRA frame. AV1 movie encoding also defines INTER frames,
+which are predictions of one or more other frames. INTER frames are not used in AVIF and therefore this coded ignores INTER frames.
+
+An OBU section for AVIF consists of the following headers:
+
+## Temporal delimiter
+
+In AV1 movies this is a time point. Although irrelevant for AVIF, most implementtions write one such delimiter at the start of the section.
+
+## Sequence header
+
+Common herader for a list (or sequence) of frames. For AVIF, this is exaclty 1 frame. For AVIF, this header can be reduced in size when its `ReducedStillPictureHerader` parameter is true.
+This setting is recommended, as all the extra parameters are not applicable for AVIF.
+
+## Frame header
+
+Can be 3 different OBU types, which define a single INTRA frame in AVIF files.
+
+## Tile group
+
+Defines the tiling parameters and contains the parameters its tile using a different coding.
+
+# Tiling
+
+In AV1 a frame is made up of 1 or more tiles. The parameters for each tile are entropy encoded using the context aware symbol coding.
+These parameters are contained in an OBU tile group header.
+
+## Superblock
+
+A tile consists of one or more superblocks. Superblocks can be either 64x64 or 128x128 pixels in size.
+This choice is made per frame, and is specified in the `ObuFrameHeader`.
+A superblock contains one or more partitions, to further devide the area.
+
+## Partition
+
+A superblock contains one or more Partitions. The partition Type determines the number of partitions it is further split in.
+Paritions can contain other partitions and blocks.
+
+## Block
+
+
+## Transform Block
+
+A Transform Block is the smallest area of the image, which has the same transformation parameters. A block contains ore or more ModeInfos.
+
+
+## ModeInfo
+
+The smallest unit in the frame. It determines the parameters for an area of 4 by 4 pixels.
+
+# References
+
+[AV1 embedded in HEIF](https://aomediacodec.github.io/av1-isobmff)
+
+[AV1 specification](https://aomediacodec.github.io/av1-spec/av1-spec.pdf)
+
+[AVIF specification](https://aomediacodec.github.io/av1-avif)
+
+[AV1/AVIF reference implementation](http://gitlab.com/AOMediaCodec/SVT-AV1)
+
+[AOM's original development implementation](https://github.com/AOMediaCodec/libavif)
+
+[Paper describing the techniques used in AV1](https://arxiv.org/pdf/2008.06091)
+
+# Test images
+
+[Netflix image repository](http://download.opencontent.netflix.com/?prefix=AV1/)
+
+[AVIF sample images](https://github.com/link-u/avif-sample-images)
+
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs
new file mode 100644
index 0000000000..3f04731506
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1BlockModeInfo
+{
+ private int[] paletteSize;
+
+ public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point positionInSuperblock)
+ {
+ this.BlockSize = blockSize;
+ this.PositionInSuperblock = positionInSuperblock;
+ this.AngleDelta = new int[numPlanes - 1];
+ this.paletteSize = new int[numPlanes - 1];
+ this.FilterIntraModeInfo = new();
+ this.FirstTransformLocation = new int[numPlanes - 1];
+ this.TransformUnitsCount = new int[numPlanes - 1];
+ }
+
+ public Av1BlockSize BlockSize { get; }
+
+ ///
+ /// Gets or sets the for the luminance channel.
+ ///
+ public Av1PredictionMode YMode { get; set; }
+
+ public bool Skip { get; set; }
+
+ public Av1PartitionType PartitionType { get; set; }
+
+ public bool SkipMode { get; set; }
+
+ public int SegmentId { get; set; }
+
+ ///
+ /// Gets or sets the for the chroma channels.
+ ///
+ public Av1PredictionMode UvMode { get; set; }
+
+ public bool UseUltraBlockCopy { get; set; }
+
+ public int ChromaFromLumaAlphaIndex { get; set; }
+
+ public int ChromaFromLumaAlphaSign { get; set; }
+
+ public int[] AngleDelta { get; set; }
+
+ ///
+ /// Gets the position relative to the Superblock, counted in mode info (4x4 pixels).
+ ///
+ public Point PositionInSuperblock { get; }
+
+ public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; }
+
+ ///
+ /// Gets the index of the first of this Mode Info in the .
+ ///
+ public int[] FirstTransformLocation { get; }
+
+ public int[] TransformUnitsCount { get; internal set; }
+
+ public int GetPaletteSize(Av1Plane plane) => this.paletteSize[Math.Min(1, (int)plane)];
+
+ public int GetPaletteSize(Av1PlaneType planeType) => this.paletteSize[(int)planeType];
+
+ public void SetPaletteSizes(int ySize, int uvSize) => this.paletteSize = [ySize, uvSize];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs
new file mode 100644
index 0000000000..b2c5259e10
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs
@@ -0,0 +1,2061 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal static class Av1DefaultDistributions
+{
+ public static Av1Distribution[] FrameYMode =>
+ [
+ new(22801, 23489, 24293, 24756, 25601, 26123, 26606, 27418, 27945, 29228, 29685, 30349),
+ new(18673, 19845, 22631, 23318, 23950, 24649, 25527, 27364, 28152, 29701, 29984, 30852),
+ new(19770, 20979, 23396, 23939, 24241, 24654, 25136, 27073, 27830, 29360, 29730, 30659),
+ new(20155, 21301, 22838, 23178, 23261, 23533, 23703, 24804, 25352, 26575, 27016, 28049)
+ ];
+
+ public static Av1Distribution[][] FilterYMode =>
+ [
+ [
+ new(15588, 17027, 19338, 20218, 20682, 21110, 21825, 23244, 24189, 28165, 29093, 30466),
+ new(12016, 18066, 19516, 20303, 20719, 21444, 21888, 23032, 24434, 28658, 30172, 31409),
+ new(10052, 10771, 22296, 22788, 23055, 23239, 24133, 25620, 26160, 29336, 29929, 31567),
+ new(14091, 15406, 16442, 18808, 19136, 19546, 19998, 22096, 24746, 29585, 30958, 32462),
+ new(12122, 13265, 15603, 16501, 18609, 20033, 22391, 25583, 26437, 30261, 31073, 32475)
+ ], [
+ new(10023, 19585, 20848, 21440, 21832, 22760, 23089, 24023, 25381, 29014, 30482, 31436),
+ new(5983, 24099, 24560, 24886, 25066, 25795, 25913, 26423, 27610, 29905, 31276, 31794),
+ new(7444, 12781, 20177, 20728, 21077, 21607, 22170, 23405, 24469, 27915, 29090, 30492),
+ new(8537, 14689, 15432, 17087, 17408, 18172, 18408, 19825, 24649, 29153, 31096, 32210),
+ new(7543, 14231, 15496, 16195, 17905, 20717, 21984, 24516, 26001, 29675, 30981, 31994)
+ ], [
+ new(12613, 13591, 21383, 22004, 22312, 22577, 23401, 25055, 25729, 29538, 30305, 32077),
+ new(9687, 13470, 18506, 19230, 19604, 20147, 20695, 22062, 23219, 27743, 29211, 30907),
+ new(6183, 6505, 26024, 26252, 26366, 26434, 27082, 28354, 28555, 30467, 30794, 32086),
+ new(10718, 11734, 14954, 17224, 17565, 17924, 18561, 21523, 23878, 28975, 30287, 32252),
+ new(9194, 9858, 16501, 17263, 18424, 19171, 21563, 25961, 26561, 30072, 30737, 32463)
+ ], [
+ new(12602, 14399, 15488, 18381, 18778, 19315, 19724, 21419, 25060, 29696, 30917, 32409),
+ new(8203, 13821, 14524, 17105, 17439, 18131, 18404, 19468, 25225, 29485, 31158, 32342),
+ new(8451, 9731, 15004, 17643, 18012, 18425, 19070, 21538, 24605, 29118, 30078, 32018),
+ new(7714, 9048, 9516, 16667, 16817, 16994, 17153, 18767, 26743, 30389, 31536, 32528),
+ new(8843, 10280, 11496, 15317, 16652, 17943, 19108, 22718, 25769, 29953, 30983, 32485)
+ ], [
+ new(12578, 13671, 15979, 16834, 19075, 20913, 22989, 25449, 26219, 30214, 31150, 32477),
+ new(9563, 13626, 15080, 15892, 17756, 20863, 22207, 24236, 25380, 29653, 31143, 32277),
+ new(8356, 8901, 17616, 18256, 19350, 20106, 22598, 25947, 26466, 29900, 30523, 32261),
+ new(10835, 11815, 13124, 16042, 17018, 18039, 18947, 22753, 24615, 29489, 30883, 32482),
+ new(7618, 8288, 9859, 10509, 15386, 18657, 22903, 28776, 29180, 31355, 31802, 32593)
+ ]
+ ];
+
+ public static Av1Distribution[][] UvMode =>
+ [
+ [
+ new(22631, 24152, 25378, 25661, 25986, 26520, 27055, 27923, 28244, 30059, 30941, 31961),
+ new(9513, 26881, 26973, 27046, 27118, 27664, 27739, 27824, 28359, 29505, 29800, 31796),
+ new(9845, 9915, 28663, 28704, 28757, 28780, 29198, 29822, 29854, 30764, 31777, 32029),
+ new(13639, 13897, 14171, 25331, 25606, 25727, 25953, 27148, 28577, 30612, 31355, 32493),
+ new(9764, 9835, 9930, 9954, 25386, 27053, 27958, 28148, 28243, 31101, 31744, 32363),
+ new(11825, 13589, 13677, 13720, 15048, 29213, 29301, 29458, 29711, 31161, 31441, 32550),
+ new(14175, 14399, 16608, 16821, 17718, 17775, 28551, 30200, 30245, 31837, 32342, 32667),
+ new(12885, 13038, 14978, 15590, 15673, 15748, 16176, 29128, 29267, 30643, 31961, 32461),
+ new(12026, 13661, 13874, 15305, 15490, 15726, 15995, 16273, 28443, 30388, 30767, 32416),
+ new(19052, 19840, 20579, 20916, 21150, 21467, 21885, 22719, 23174, 28861, 30379, 32175),
+ new(18627, 19649, 20974, 21219, 21492, 21816, 22199, 23119, 23527, 27053, 31397, 32148),
+ new(17026, 19004, 19997, 20339, 20586, 21103, 21349, 21907, 22482, 25896, 26541, 31819),
+ new(12124, 13759, 14959, 14992, 15007, 15051, 15078, 15166, 15255, 15753, 16039, 16606)
+ ], [
+ new(10407, 11208, 12900, 13181, 13823, 14175, 14899, 15656, 15986, 20086, 20995, 22455, 24212),
+ new(4532, 19780, 20057, 20215, 20428, 21071, 21199, 21451, 22099, 24228, 24693, 27032, 29472),
+ new(5273, 5379, 20177, 20270, 20385, 20439, 20949, 21695, 21774, 23138, 24256, 24703, 26679),
+ new(6740, 7167, 7662, 14152, 14536, 14785, 15034, 16741, 18371, 21520, 22206, 23389, 24182),
+ new(4987, 5368, 5928, 6068, 19114, 20315, 21857, 22253, 22411, 24911, 25380, 26027, 26376),
+ new(5370, 6889, 7247, 7393, 9498, 21114, 21402, 21753, 21981, 24780, 25386, 26517, 27176),
+ new(4816, 4961, 7204, 7326, 8765, 8930, 20169, 20682, 20803, 23188, 23763, 24455, 24940),
+ new(6608, 6740, 8529, 9049, 9257, 9356, 9735, 18827, 19059, 22336, 23204, 23964, 24793),
+ new(5998, 7419, 7781, 8933, 9255, 9549, 9753, 10417, 18898, 22494, 23139, 24764, 25989),
+ new(10660, 11298, 12550, 12957, 13322, 13624, 14040, 15004, 15534, 20714, 21789, 23443, 24861),
+ new(10522, 11530, 12552, 12963, 13378, 13779, 14245, 15235, 15902, 20102, 22696, 23774, 25838),
+ new(10099, 10691, 12639, 13049, 13386, 13665, 14125, 15163, 15636, 19676, 20474, 23519, 25208),
+ new(3144, 5087, 7382, 7504, 7593, 7690, 7801, 8064, 8232, 9248, 9875, 10521, 29048)
+ ]
+ ];
+
+ public static Av1Distribution[] AngleDelta =>
+ [
+ new(2180, 5032, 7567, 22776, 26989, 30217),
+ new(2301, 5608, 8801, 23487, 26974, 30330),
+ new(3780, 11018, 13699, 19354, 23083, 31286),
+ new(4581, 11226, 15147, 17138, 21834, 28397),
+ new(1737, 10927, 14509, 19588, 22745, 28823),
+ new(2664, 10176, 12485, 17650, 21600, 30495),
+ new(2240, 11096, 15453, 20341, 22561, 28917),
+ new(3605, 10428, 12459, 17676, 21244, 30655)
+ ];
+
+ public static Av1Distribution IntraBlockCopy => new(30531);
+
+ public static Av1Distribution[] PartitionTypes =>
+ [
+ new(19132, 25510, 30392),
+ new(13928, 19855, 28540),
+ new(12522, 23679, 28629),
+ new(9896, 18783, 25853),
+ new(15597, 20929, 24571, 26706, 27664, 28821, 29601, 30571, 31902),
+ new(7925, 11043, 16785, 22470, 23971, 25043, 26651, 28701, 29834),
+ new(5414, 13269, 15111, 20488, 22360, 24500, 25537, 26336, 32117),
+ new(2662, 6362, 8614, 20860, 23053, 24778, 26436, 27829, 31171),
+ new(18462, 20920, 23124, 27647, 28227, 29049, 29519, 30178, 31544),
+ new(7689, 9060, 12056, 24992, 25660, 26182, 26951, 28041, 29052),
+ new(6015, 9009, 10062, 24544, 25409, 26545, 27071, 27526, 32047),
+ new(1394, 2208, 2796, 28614, 29061, 29466, 29840, 30185, 31899),
+ new(20137, 21547, 23078, 29566, 29837, 30261, 30524, 30892, 31724),
+ new(6732, 7490, 9497, 27944, 28250, 28515, 28969, 29630, 30104),
+ new(5945, 7663, 8348, 28683, 29117, 29749, 30064, 30298, 32238),
+ new(870, 1212, 1487, 31198, 31394, 31574, 31743, 31881, 32332),
+ new(27899, 28219, 28529, 32484, 32539, 32619, 32639),
+ new(6607, 6990, 8268, 32060, 32219, 32338, 32371),
+ new(5429, 6676, 7122, 32027, 32227, 32531, 32582),
+ new(711, 966, 1172, 32448, 32538, 32617, 32664)
+ ];
+
+ public static Av1Distribution[] Skip => [new(31671), new(16515), new(4576)];
+
+ public static Av1Distribution DeltaLoopFilterAbsolute => new(28160, 32120, 32677);
+
+ public static Av1Distribution DeltaQuantizerAbsolute => new(28160, 32120, 32677);
+
+ public static Av1Distribution[] SegmentId => [new(128 * 128), new(128 * 128), new(128 * 128)];
+
+ public static Av1Distribution[][] KeyFrameYMode =>
+ [
+ [
+ new(15588, 17027, 19338, 20218, 20682, 21110, 21825, 23244, 24189, 28165, 29093, 30466),
+ new(12016, 18066, 19516, 20303, 20719, 21444, 21888, 23032, 24434, 28658, 30172, 31409),
+ new(10052, 10771, 22296, 22788, 23055, 23239, 24133, 25620, 26160, 29336, 29929, 31567),
+ new(14091, 15406, 16442, 18808, 19136, 19546, 19998, 22096, 24746, 29585, 30958, 32462),
+ new(12122, 13265, 15603, 16501, 18609, 20033, 22391, 25583, 26437, 30261, 31073, 32475),
+ ], [
+ new(10023, 19585, 20848, 21440, 21832, 22760, 23089, 24023, 25381, 29014, 30482, 31436),
+ new(5983, 24099, 24560, 24886, 25066, 25795, 25913, 26423, 27610, 29905, 31276, 31794),
+ new(7444, 12781, 20177, 20728, 21077, 21607, 22170, 23405, 24469, 27915, 29090, 30492),
+ new(8537, 14689, 15432, 17087, 17408, 18172, 18408, 19825, 24649, 29153, 31096, 32210),
+ new(7543, 14231, 15496, 16195, 17905, 20717, 21984, 24516, 26001, 29675, 30981, 31994)
+ ], [
+ new(12613, 13591, 21383, 22004, 22312, 22577, 23401, 25055, 25729, 29538, 30305, 32077),
+ new(9687, 13470, 18506, 19230, 19604, 20147, 20695, 22062, 23219, 27743, 29211, 30907),
+ new(6183, 6505, 26024, 26252, 26366, 26434, 27082, 28354, 28555, 30467, 30794, 32086),
+ new(10718, 11734, 14954, 17224, 17565, 17924, 18561, 21523, 23878, 28975, 30287, 32252),
+ new(9194, 9858, 16501, 17263, 18424, 19171, 21563, 25961, 26561, 30072, 30737, 32463)
+ ], [
+ new(12602, 14399, 15488, 18381, 18778, 19315, 19724, 21419, 25060, 29696, 30917, 32409),
+ new(8203, 13821, 14524, 17105, 17439, 18131, 18404, 19468, 25225, 29485, 31158, 32342),
+ new(8451, 9731, 15004, 17643, 18012, 18425, 19070, 21538, 24605, 29118, 30078, 32018),
+ new(7714, 9048, 9516, 16667, 16817, 16994, 17153, 18767, 26743, 30389, 31536, 32528),
+ new(8843, 10280, 11496, 15317, 16652, 17943, 19108, 22718, 25769, 29953, 30983, 32485)
+ ], [
+ new(12578, 13671, 15979, 16834, 19075, 20913, 22989, 25449, 26219, 30214, 31150, 32477),
+ new(9563, 13626, 15080, 15892, 17756, 20863, 22207, 24236, 25380, 29653, 31143, 32277),
+ new(8356, 8901, 17616, 18256, 19350, 20106, 22598, 25947, 26466, 29900, 30523, 32261),
+ new(10835, 11815, 13124, 16042, 17018, 18039, 18947, 22753, 24615, 29489, 30883, 32482),
+ new(7618, 8288, 9859, 10509, 15386, 18657, 22903, 28776, 29180, 31355, 31802, 32593)
+ ]
+ ];
+
+ public static Av1Distribution FilterIntraMode => new(8949, 12776, 17211, 29558);
+
+ public static Av1Distribution[] FilterIntra =>
+ [
+ new(4621), new(6743), new(5893), new(7866), new(12551), new(9394),
+ new(12408), new(14301), new(12756), new(22343), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(12770), new(10368),
+ new(20229), new(18101), new(16384), new(16384)
+ ];
+
+ public static Av1Distribution[][] TransformSize =>
+ [
+ [new(19968), new(19968), new(24320)],
+ [new(12272, 30172), new(12272, 30172), new(18677, 30848)],
+ [new(12986, 15180), new(12986, 15180), new(24302, 25602)],
+ [new(5782, 11475), new(5782, 11475), new(16803, 22759)],
+ ];
+
+ private static Av1Distribution[][][] EndOfBlockFlagMulti16 =>
+ [
+ [
+ [new(840, 1039, 1980, 4895), new(370, 671, 1883, 4471)],
+ [new(3247, 4950, 9688, 14563), new(1904, 3354, 7763, 14647)]
+ ],
+ [
+ [new(2125, 2551, 5165, 8946), new(513, 765, 1859, 6339)],
+ [new(7637, 9498, 14259, 19108), new(2497, 4096, 8866, 16993)]
+ ],
+ [
+ [new(4016, 4897, 8881, 14968), new(716, 1105, 2646, 10056)],
+ [new(11139, 13270, 18241, 23566), new(3192, 5032, 10297, 19755)]
+ ],
+ [
+ [new(6708, 8958, 14746, 22133), new(1222, 2074, 4783, 15410)],
+ [new(19575, 21766, 26044, 29709), new(7297, 10767, 19273, 28194)]
+ ]
+ ];
+
+ private static Av1Distribution[][][] EndOfBlockFlagMulti32 =>
+ [
+ [
+ [new(400, 520, 977, 2102, 6542), new(210, 405, 1315, 3326, 7537)],
+ [new(2636, 4273, 7588, 11794, 20401), new(1786, 3179, 6902, 11357, 19054)]
+ ],
+ [
+ [new(989, 1249, 2019, 4151, 10785), new(313, 441, 1099, 2917, 8562)],
+ [new(8394, 10352, 13932, 18855, 26014), new(2578, 4124, 8181, 13670, 24234)]
+ ],
+ [
+ [new(2515, 3003, 4452, 8162, 16041), new(574, 821, 1836, 5089, 13128)],
+ [new(13468, 16303, 20361, 25105, 29281), new(3542, 5502, 10415, 16760, 25644)]
+ ],
+ [
+ [new(4617, 5709, 8446, 13584, 23135), new(1156, 1702, 3675, 9274, 20539)],
+ [new(22086, 24282, 27010, 29770, 31743), new(7699, 10897, 20891, 26926, 31628)]
+ ]
+ ];
+
+ private static Av1Distribution[][][] EndOfBlockFlagMulti64 =>
+ [
+ [
+ [new(329, 498, 1101, 1784, 3265, 7758), new(335, 730, 1459, 5494, 8755, 12997)],
+ [new(3505, 5304, 10086, 13814, 17684, 23370), new(1563, 2700, 4876, 10911, 14706, 22480)],
+ ],
+ [
+ [new(1260, 1446, 2253, 3712, 6652, 13369), new(401, 605, 1029, 2563, 5845, 12626)],
+ [new(8609, 10612, 14624, 18714, 22614, 29024), new(1923, 3127, 5867, 9703, 14277, 27100)]
+ ],
+ [
+ [new(2374, 2772, 4583, 7276, 12288, 19706), new(497, 810, 1315, 3000, 7004, 15641)],
+ [new(15050, 17126, 21410, 24886, 28156, 30726), new(4034, 6290, 10235, 14982, 21214, 28491)]
+ ],
+ [
+ [new(6307, 7541, 12060, 16358, 22553, 27865), new(1289, 2320, 3971, 7926, 14153, 24291)],
+ [new(24212, 25708, 28268, 30035, 31307, 32049), new(8726, 12378, 19409, 26450, 30038, 32462)]
+ ]
+ ];
+
+ private static Av1Distribution[][][] EndOfBlockFlagMulti128 =>
+ [
+ [
+ [new(219, 482, 1140, 2091, 3680, 6028, 12586), new(371, 699, 1254, 4830, 9479, 12562, 17497)],
+ [new(5245, 7456, 12880, 15852, 20033, 23932, 27608), new(2054, 3472, 5869, 14232, 18242, 20590, 26752)]
+ ],
+ [
+ [new(685, 933, 1488, 2714, 4766, 8562, 19254), new(217, 352, 618, 2303, 5261, 9969, 17472)],
+ [new(8045, 11200, 15497, 19595, 23948, 27408, 30938), new(2310, 4160, 7471, 14997, 17931, 20768, 30240)]
+ ],
+ [
+ [new(1366, 1738, 2527, 5016, 9355, 15797, 24643), new(354, 558, 944, 2760, 7287, 14037, 21779)],
+ [new(13627, 16246, 20173, 24429, 27948, 30415, 31863), new(6275, 9889, 14769, 23164, 27988, 30493, 32272)]
+ ],
+ [
+ [new(3472, 4885, 7489, 12481, 18517, 24536, 29635), new(886, 1731, 3271, 8469, 15569, 22126, 28383)],
+ [new(24313, 26062, 28385, 30107, 31217, 31898, 32345), new(9165, 13282, 21150, 30286, 31894, 32571, 32712)]
+ ]
+ ];
+
+ private static Av1Distribution[][][] EndOfBlockFlagMulti256 =>
+ [
+ [
+ [
+ new(310, 584, 1887, 3589, 6168, 8611, 11352, 15652),
+ new(998, 1850, 2998, 5604, 17341, 19888, 22899, 25583),
+ ],
+ [
+ new(2520, 3240, 5952, 8870, 12577, 17558, 19954, 24168),
+ new(2203, 4130, 7435, 10739, 20652, 23681, 25609, 27261)
+ ],
+ ],
+ [
+ [
+ new(1448, 2109, 4151, 6263, 9329, 13260, 17944, 23300),
+ new(399, 1019, 1749, 3038, 10444, 15546, 22739, 27294)
+ ],
+ [
+ new(6402, 8148, 12623, 15072, 18728, 22847, 26447, 29377),
+ new(1674, 3252, 5734, 10159, 22397, 23802, 24821, 30940)
+ ]
+ ],
+ [
+ [
+ new(3089, 3920, 6038, 9460, 14266, 19881, 25766, 29176),
+ new(1084, 2358, 3488, 5122, 11483, 18103, 26023, 29799)
+ ],
+ [
+ new(11514, 13794, 17480, 20754, 24361, 27378, 29492, 31277),
+ new(6571, 9610, 15516, 21826, 29092, 30829, 31842, 32708)
+ ]
+ ],
+ [
+ [
+ new(5348, 7113, 11820, 15924, 22106, 26777, 30334, 31757),
+ new(2453, 4474, 6307, 8777, 16474, 22975, 29000, 31547)
+ ],
+ [
+ new(23110, 24597, 27140, 28894, 30167, 30927, 31392, 32094),
+ new(9998, 17661, 25178, 28097, 31308, 32038, 32403, 32695)
+ ]
+ ]
+ ];
+
+ private static Av1Distribution[][][] EndOfBlockFlagMulti512 =>
+ [
+ [
+ [
+ new(641, 983, 3707, 5430, 10234, 14958, 18788, 23412, 26061),
+ new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491)
+ ],
+ [
+ new(5095, 6446, 9996, 13354, 16017, 17986, 20919, 26129, 29140),
+ new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491)
+ ]
+ ],
+ [
+ [
+ new(1230, 2278, 5035, 7776, 11871, 15346, 19590, 24584, 28749),
+ new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491)
+ ],
+ [
+ new(7265, 9979, 15819, 19250, 21780, 23846, 26478, 28396, 31811),
+ new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491)
+ ]
+ ],
+ [
+ [
+ new(2624, 3936, 6480, 9686, 13979, 17726, 23267, 28410, 31078),
+ new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491)
+ ],
+ [
+ new(12015, 14769, 19588, 22052, 24222, 25812, 27300, 29219, 32114),
+ new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491)
+ ]
+ ],
+ [
+ [
+ new(5927, 7809, 10923, 14597, 19439, 24135, 28456, 31142, 32060),
+ new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491)
+ ],
+ [
+ new(21093, 23043, 25742, 27658, 29097, 29716, 30073, 30820, 31956),
+ new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491)
+ ]
+ ]
+ ];
+
+ private static Av1Distribution[][][] EndOfBlockFlagMulti1024 =>
+ [
+ [
+ [
+ new(393, 421, 751, 1623, 3160, 6352, 13345, 18047, 22571, 25830),
+ new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789)
+ ],
+ [
+ new(1865, 1988, 2930, 4242, 10533, 16538, 21354, 27255, 28546, 31784),
+ new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789)
+ ]
+ ],
+ [
+ [
+ new(696, 948, 3145, 5702, 9706, 13217, 17851, 21856, 25692, 28034),
+ new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789)
+ ],
+ [
+ new(2672, 3591, 9330, 17084, 22725, 24284, 26527, 28027, 28377, 30876),
+ new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789)
+ ]
+ ],
+ [
+ [
+ new(2784, 3831, 7041, 10521, 14847, 18844, 23155, 26682, 29229, 31045),
+ new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789)
+ ],
+ [
+ new(9577, 12466, 17739, 20750, 22061, 23215, 24601, 25483, 25843, 32056),
+ new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789)
+ ]
+ ],
+ [
+ [
+ new(6698, 8334, 11961, 15762, 20186, 23862, 27434, 29326, 31082, 32050),
+ new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789)
+ ],
+ [
+ new(20569, 22426, 25569, 26859, 28053, 28913, 29486, 29724, 29807, 32570),
+ new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789)
+ ]
+ ]
+ ];
+
+ private static Av1Distribution[][][][] CoefficientsBaseRange =>
+ [
+ [
+ [
+ [
+ new(14298, 20718, 24174), new(12536, 19601, 23789), new(8712, 15051, 19503),
+ new(6170, 11327, 15434), new(4742, 8926, 12538), new(3803, 7317, 10546),
+ new(1696, 3317, 4871), new(14392, 19951, 22756), new(15978, 23218, 26818),
+ new(12187, 19474, 23889), new(9176, 15640, 20259), new(7068, 12655, 17028),
+ new(5656, 10442, 14472), new(2580, 4992, 7244), new(12136, 18049, 21426),
+ new(13784, 20721, 24481), new(10836, 17621, 21900), new(8372, 14444, 18847),
+ new(6523, 11779, 16000), new(5337, 9898, 13760), new(3034, 5860, 8462)
+ ],
+ [
+ new(15967, 22905, 26286), new(13534, 20654, 24579), new(9504, 16092, 20535),
+ new(6975, 12568, 16903), new(5364, 10091, 14020), new(4357, 8370, 11857),
+ new(2506, 4934, 7218), new(23032, 28815, 30936), new(19540, 26704, 29719),
+ new(15158, 22969, 27097), new(11408, 18865, 23650), new(8885, 15448, 20250),
+ new(7108, 12853, 17416), new(4231, 8041, 11480), new(19823, 26490, 29156),
+ new(18890, 25929, 28932), new(15660, 23491, 27433), new(12147, 19776, 24488),
+ new(9728, 16774, 21649), new(7919, 14277, 19066), new(5440, 10170, 14185)
+ ]
+ ],
+ [
+ [
+ new(14406, 20862, 24414), new(11824, 18907, 23109), new(8257, 14393, 18803),
+ new(5860, 10747, 14778), new(4475, 8486, 11984), new(3606, 6954, 10043),
+ new(1736, 3410, 5048), new(14430, 20046, 22882), new(15593, 22899, 26709),
+ new(12102, 19368, 23811), new(9059, 15584, 20262), new(6999, 12603, 17048),
+ new(5684, 10497, 14553), new(2822, 5438, 7862), new(15785, 21585, 24359),
+ new(18347, 25229, 28266), new(14974, 22487, 26389), new(11423, 18681, 23271),
+ new(8863, 15350, 20008), new(7153, 12852, 17278), new(3707, 7036, 9982)
+ ],
+ [
+ new(15460, 21696, 25469), new(12170, 19249, 23191), new(8723, 15027, 19332),
+ new(6428, 11704, 15874), new(4922, 9292, 13052), new(4139, 7695, 11010),
+ new(2291, 4508, 6598), new(19856, 26920, 29828), new(17923, 25289, 28792),
+ new(14278, 21968, 26297), new(10910, 18136, 22950), new(8423, 14815, 19627),
+ new(6771, 12283, 16774), new(4074, 7750, 11081), new(19852, 26074, 28672),
+ new(19371, 26110, 28989), new(16265, 23873, 27663), new(12758, 20378, 24952),
+ new(10095, 17098, 21961), new(8250, 14628, 19451), new(5205, 9745, 13622)
+ ]
+ ],
+ [
+ [
+ new(10563, 16233, 19763), new(9794, 16022, 19804), new(6750, 11945, 15759),
+ new(4963, 9186, 12752), new(3845, 7435, 10627), new(3051, 6085, 8834),
+ new(1311, 2596, 3830), new(11246, 16404, 19689), new(12315, 18911, 22731),
+ new(10557, 17095, 21289), new(8136, 14006, 18249), new(6348, 11474, 15565),
+ new(5196, 9655, 13400), new(2349, 4526, 6587), new(13337, 18730, 21569),
+ new(19306, 26071, 28882), new(15952, 23540, 27254), new(12409, 19934, 24430),
+ new(9760, 16706, 21389), new(8004, 14220, 18818), new(4138, 7794, 10961)
+ ],
+ [
+ new(10870, 16684, 20949), new(9664, 15230, 18680), new(6886, 12109, 15408),
+ new(4825, 8900, 12305), new(3630, 7162, 10314), new(3036, 6429, 9387),
+ new(1671, 3296, 4940), new(13819, 19159, 23026), new(11984, 19108, 23120),
+ new(10690, 17210, 21663), new(7984, 14154, 18333), new(6868, 12294, 16124),
+ new(5274, 8994, 12868), new(2988, 5771, 8424), new(19736, 26647, 29141),
+ new(18933, 26070, 28984), new(15779, 23048, 27200), new(12638, 20061, 24532),
+ new(10692, 17545, 22220), new(9217, 15251, 20054), new(5078, 9284, 12594)
+ ]
+ ],
+ [
+ [
+ new(2331, 3662, 5244), new(2891, 4771, 6145), new(4598, 7623, 9729),
+ new(3520, 6845, 9199), new(3417, 6119, 9324), new(2601, 5412, 7385),
+ new(600, 1173, 1744), new(7672, 13286, 17469), new(4232, 7792, 10793),
+ new(2915, 5317, 7397), new(2318, 4356, 6152), new(2127, 4000, 5554),
+ new(1850, 3478, 5275), new(977, 1933, 2843), new(18280, 24387, 27989),
+ new(15852, 22671, 26185), new(13845, 20951, 24789), new(11055, 17966, 22129),
+ new(9138, 15422, 19801), new(7454, 13145, 17456), new(3370, 6393, 9013)
+ ],
+ [
+ new(5842, 9229, 10838), new(2313, 3491, 4276), new(2998, 6104, 7496),
+ new(2420, 7447, 9868), new(3034, 8495, 10923), new(4076, 8937, 10975),
+ new(1086, 2370, 3299), new(9714, 17254, 20444), new(8543, 13698, 17123),
+ new(4918, 9007, 11910), new(4129, 7532, 10553), new(2364, 5533, 8058),
+ new(1834, 3546, 5563), new(1473, 2908, 4133), new(15405, 21193, 25619),
+ new(15691, 21952, 26561), new(12962, 19194, 24165), new(10272, 17855, 22129),
+ new(8588, 15270, 20718), new(8682, 14669, 19500), new(4870, 9636, 13205)
+ ]
+ ],
+ [
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ new(14995, 21341, 24749), new(13158, 20289, 24601), new(8941, 15326, 19876),
+ new(6297, 11541, 15807), new(4817, 9029, 12776), new(3731, 7273, 10627),
+ new(1847, 3617, 5354), new(14472, 19659, 22343), new(16806, 24162, 27533),
+ new(12900, 20404, 24713), new(9411, 16112, 20797), new(7056, 12697, 17148),
+ new(5544, 10339, 14460), new(2954, 5704, 8319), new(12464, 18071, 21354),
+ new(15482, 22528, 26034), new(12070, 19269, 23624), new(8953, 15406, 20106),
+ new(7027, 12730, 17220), new(5887, 10913, 15140), new(3793, 7278, 10447)
+ ],
+ [
+ new(15571, 22232, 25749), new(14506, 21575, 25374), new(10189, 17089, 21569),
+ new(7316, 13301, 17915), new(5783, 10912, 15190), new(4760, 9155, 13088),
+ new(2993, 5966, 8774), new(23424, 28903, 30778), new(20775, 27666, 30290),
+ new(16474, 24410, 28299), new(12471, 20180, 24987), new(9410, 16487, 21439),
+ new(7536, 13614, 18529), new(5048, 9586, 13549), new(21090, 27290, 29756),
+ new(20796, 27402, 30026), new(17819, 25485, 28969), new(13860, 21909, 26462),
+ new(11002, 18494, 23529), new(8953, 15929, 20897), new(6448, 11918, 16454)
+ ]
+ ],
+ [
+ [
+ new(15999, 22208, 25449), new(13050, 19988, 24122), new(8594, 14864, 19378),
+ new(6033, 11079, 15238), new(4554, 8683, 12347), new(3672, 7139, 10337),
+ new(1900, 3771, 5576), new(15788, 21340, 23949), new(16825, 24235, 27758),
+ new(12873, 20402, 24810), new(9590, 16363, 21094), new(7352, 13209, 17733),
+ new(5960, 10989, 15184), new(3232, 6234, 9007), new(15761, 20716, 23224),
+ new(19318, 25989, 28759), new(15529, 23094, 26929), new(11662, 18989, 23641),
+ new(8955, 15568, 20366), new(7281, 13106, 17708), new(4248, 8059, 11440)
+ ],
+ [
+ new(14899, 21217, 24503), new(13519, 20283, 24047), new(9429, 15966, 20365),
+ new(6700, 12355, 16652), new(5088, 9704, 13716), new(4243, 8154, 11731),
+ new(2702, 5364, 7861), new(22745, 28388, 30454), new(20235, 27146, 29922),
+ new(15896, 23715, 27637), new(11840, 19350, 24131), new(9122, 15932, 20880),
+ new(7488, 13581, 18362), new(5114, 9568, 13370), new(20845, 26553, 28932),
+ new(20981, 27372, 29884), new(17781, 25335, 28785), new(13760, 21708, 26297),
+ new(10975, 18415, 23365), new(9045, 15789, 20686), new(6130, 11199, 15423)
+ ]
+ ],
+ [
+ [
+ new(13549, 19724, 23158), new(11844, 18382, 22246), new(7919, 13619, 17773),
+ new(5486, 10143, 13946), new(4166, 7983, 11324), new(3364, 6506, 9427),
+ new(1598, 3160, 4674), new(15281, 20979, 23781), new(14939, 22119, 25952),
+ new(11363, 18407, 22812), new(8609, 14857, 19370), new(6737, 12184, 16480),
+ new(5506, 10263, 14262), new(2990, 5786, 8380), new(20249, 25253, 27417),
+ new(21070, 27518, 30001), new(16854, 24469, 28074), new(12864, 20486, 25000),
+ new(9962, 16978, 21778), new(8074, 14338, 19048), new(4494, 8479, 11906)
+ ],
+ [
+ new(13960, 19617, 22829), new(11150, 17341, 21228), new(7150, 12964, 17190),
+ new(5331, 10002, 13867), new(4167, 7744, 11057), new(3480, 6629, 9646),
+ new(1883, 3784, 5686), new(18752, 25660, 28912), new(16968, 24586, 28030),
+ new(13520, 21055, 25313), new(10453, 17626, 22280), new(8386, 14505, 19116),
+ new(6742, 12595, 17008), new(4273, 8140, 11499), new(22120, 27827, 30233),
+ new(20563, 27358, 29895), new(17076, 24644, 28153), new(13362, 20942, 25309),
+ new(10794, 17965, 22695), new(9014, 15652, 20319), new(5708, 10512, 14497)
+ ]
+ ],
+ [
+ [
+ new(5705, 10930, 15725), new(7946, 12765, 16115), new(6801, 12123, 16226),
+ new(5462, 10135, 14200), new(4189, 8011, 11507), new(3191, 6229, 9408),
+ new(1057, 2137, 3212), new(10018, 17067, 21491), new(7380, 12582, 16453),
+ new(6068, 10845, 14339), new(5098, 9198, 12555), new(4312, 8010, 11119),
+ new(3700, 6966, 9781), new(1693, 3326, 4887), new(18757, 24930, 27774),
+ new(17648, 24596, 27817), new(14707, 22052, 26026), new(11720, 18852, 23292),
+ new(9357, 15952, 20525), new(7810, 13753, 18210), new(3879, 7333, 10328)
+ ],
+ [
+ new(8278, 13242, 15922), new(10547, 15867, 18919), new(9106, 15842, 20609),
+ new(6833, 13007, 17218), new(4811, 9712, 13923), new(3985, 7352, 11128),
+ new(1688, 3458, 5262), new(12951, 21861, 26510), new(9788, 16044, 20276),
+ new(6309, 11244, 14870), new(5183, 9349, 12566), new(4389, 8229, 11492),
+ new(3633, 6945, 10620), new(3600, 6847, 9907), new(21748, 28137, 30255),
+ new(19436, 26581, 29560), new(16359, 24201, 27953), new(13961, 21693, 25871),
+ new(11544, 18686, 23322), new(9372, 16462, 20952), new(6138, 11210, 15390)
+ ]
+ ],
+ [
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ new(16138, 22223, 25509), new(15347, 22430, 26332), new(9614, 16736, 21332),
+ new(6600, 12275, 16907), new(4811, 9424, 13547), new(3748, 7809, 11420),
+ new(2254, 4587, 6890), new(15196, 20284, 23177), new(18317, 25469, 28451),
+ new(13918, 21651, 25842), new(10052, 17150, 21995), new(7499, 13630, 18587),
+ new(6158, 11417, 16003), new(4014, 7785, 11252), new(15048, 21067, 24384),
+ new(18202, 25346, 28553), new(14302, 22019, 26356), new(10839, 18139, 23166),
+ new(8715, 15744, 20806), new(7536, 13576, 18544), new(5413, 10335, 14498)
+ ],
+ [
+ new(17394, 24501, 27895), new(15889, 23420, 27185), new(11561, 19133, 23870),
+ new(8285, 14812, 19844), new(6496, 12043, 16550), new(4771, 9574, 13677),
+ new(3603, 6830, 10144), new(21656, 27704, 30200), new(21324, 27915, 30511),
+ new(17327, 25336, 28997), new(13417, 21381, 26033), new(10132, 17425, 22338),
+ new(8580, 15016, 19633), new(5694, 11477, 16411), new(24116, 29780, 31450),
+ new(23853, 29695, 31591), new(20085, 27614, 30428), new(15326, 24335, 28575),
+ new(11814, 19472, 24810), new(10221, 18611, 24767), new(7689, 14558, 20321)
+ ]
+ ],
+ [
+ [
+ new(16214, 22380, 25770), new(14213, 21304, 25295), new(9213, 15823, 20455),
+ new(6395, 11758, 16139), new(4779, 9187, 13066), new(3821, 7501, 10953),
+ new(2293, 4567, 6795), new(15859, 21283, 23820), new(18404, 25602, 28726),
+ new(14325, 21980, 26206), new(10669, 17937, 22720), new(8297, 14642, 19447),
+ new(6746, 12389, 16893), new(4324, 8251, 11770), new(16532, 21631, 24475),
+ new(20667, 27150, 29668), new(16728, 24510, 28175), new(12861, 20645, 25332),
+ new(10076, 17361, 22417), new(8395, 14940, 19963), new(5731, 10683, 14912)
+ ],
+ [
+ new(14433, 21155, 24938), new(14658, 21716, 25545), new(9923, 16824, 21557),
+ new(6982, 13052, 17721), new(5419, 10503, 15050), new(4852, 9162, 13014),
+ new(3271, 6395, 9630), new(22210, 27833, 30109), new(20750, 27368, 29821),
+ new(16894, 24828, 28573), new(13247, 21276, 25757), new(10038, 17265, 22563),
+ new(8587, 14947, 20327), new(5645, 11371, 15252), new(22027, 27526, 29714),
+ new(23098, 29146, 31221), new(19886, 27341, 30272), new(15609, 23747, 28046),
+ new(11993, 20065, 24939), new(9637, 18267, 23671), new(7625, 13801, 19144)
+ ]
+ ],
+ [
+ [
+ new(14438, 20798, 24089), new(12621, 19203, 23097), new(8177, 14125, 18402),
+ new(5674, 10501, 14456), new(4236, 8239, 11733), new(3447, 6750, 9806),
+ new(1986, 3950, 5864), new(16208, 22099, 24930), new(16537, 24025, 27585),
+ new(12780, 20381, 24867), new(9767, 16612, 21416), new(7686, 13738, 18398),
+ new(6333, 11614, 15964), new(3941, 7571, 10836), new(22819, 27422, 29202),
+ new(22224, 28514, 30721), new(17660, 25433, 28913), new(13574, 21482, 26002),
+ new(10629, 17977, 22938), new(8612, 15298, 20265), new(5607, 10491, 14596)
+ ],
+ [
+ new(13569, 19800, 23206), new(13128, 19924, 23869), new(8329, 14841, 19403),
+ new(6130, 10976, 15057), new(4682, 8839, 12518), new(3656, 7409, 10588),
+ new(2577, 5099, 7412), new(22427, 28684, 30585), new(20913, 27750, 30139),
+ new(15840, 24109, 27834), new(12308, 20029, 24569), new(10216, 16785, 21458),
+ new(8309, 14203, 19113), new(6043, 11168, 15307), new(23166, 28901, 30998),
+ new(21899, 28405, 30751), new(18413, 26091, 29443), new(15233, 23114, 27352),
+ new(12683, 20472, 25288), new(10702, 18259, 23409), new(8125, 14464, 19226)
+ ]
+ ],
+ [
+ [
+ new(9040, 14786, 18360), new(9979, 15718, 19415), new(7913, 13918, 18311),
+ new(5859, 10889, 15184), new(4593, 8677, 12510), new(3820, 7396, 10791),
+ new(1730, 3471, 5192), new(11803, 18365, 22709), new(11419, 18058, 22225),
+ new(9418, 15774, 20243), new(7539, 13325, 17657), new(6233, 11317, 15384),
+ new(5137, 9656, 13545), new(2977, 5774, 8349), new(21207, 27246, 29640),
+ new(19547, 26578, 29497), new(16169, 23871, 27690), new(12820, 20458, 25018),
+ new(10224, 17332, 22214), new(8526, 15048, 19884), new(5037, 9410, 13118)
+ ],
+ [
+ new(12339, 17329, 20140), new(13505, 19895, 23225), new(9847, 16944, 21564),
+ new(7280, 13256, 18348), new(4712, 10009, 14454), new(4361, 7914, 12477),
+ new(2870, 5628, 7995), new(20061, 25504, 28526), new(15235, 22878, 26145),
+ new(12985, 19958, 24155), new(9782, 16641, 21403), new(9456, 16360, 20760),
+ new(6855, 12940, 18557), new(5661, 10564, 15002), new(25656, 30602, 31894),
+ new(22570, 29107, 31092), new(18917, 26423, 29541), new(15940, 23649, 27754),
+ new(12803, 20581, 25219), new(11082, 18695, 23376), new(7939, 14373, 19005)
+ ]
+ ],
+ [
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ new(18315, 24289, 27551), new(16854, 24068, 27835), new(10140, 17927, 23173),
+ new(6722, 12982, 18267), new(4661, 9826, 14706), new(3832, 8165, 12294),
+ new(2795, 6098, 9245), new(17145, 23326, 26672), new(20733, 27680, 30308),
+ new(16032, 24461, 28546), new(11653, 20093, 25081), new(9290, 16429, 22086),
+ new(7796, 14598, 19982), new(6502, 12378, 17441), new(21681, 27732, 30320),
+ new(22389, 29044, 31261), new(19027, 26731, 30087), new(14739, 23755, 28624),
+ new(11358, 20778, 25511), new(10995, 18073, 24190), new(9162, 14990, 20617)
+ ],
+ [
+ new(21425, 27952, 30388), new(18062, 25838, 29034), new(11956, 19881, 24808),
+ new(7718, 15000, 20980), new(5702, 11254, 16143), new(4898, 9088, 16864),
+ new(3679, 6776, 11907), new(23294, 30160, 31663), new(24397, 29896, 31836),
+ new(19245, 27128, 30593), new(13202, 19825, 26404), new(11578, 19297, 23957),
+ new(8073, 13297, 21370), new(5461, 10923, 19745), new(27367, 30521, 31934),
+ new(24904, 30671, 31940), new(23075, 28460, 31299), new(14400, 23658, 30417),
+ new(13885, 23882, 28325), new(14746, 22938, 27853), new(5461, 16384, 27307)
+ ]
+ ],
+ [
+ [
+ new(18274, 24813, 27890), new(15537, 23149, 27003), new(9449, 16740, 21827),
+ new(6700, 12498, 17261), new(4988, 9866, 14198), new(4236, 8147, 11902),
+ new(2867, 5860, 8654), new(17124, 23171, 26101), new(20396, 27477, 30148),
+ new(16573, 24629, 28492), new(12749, 20846, 25674), new(10233, 17878, 22818),
+ new(8525, 15332, 20363), new(6283, 11632, 16255), new(20466, 26511, 29286),
+ new(23059, 29174, 31191), new(19481, 27263, 30241), new(15458, 23631, 28137),
+ new(12416, 20608, 25693), new(10261, 18011, 23261), new(8016, 14655, 19666)
+ ],
+ [
+ new(17616, 24586, 28112), new(15809, 23299, 27155), new(10767, 18890, 23793),
+ new(7727, 14255, 18865), new(6129, 11926, 16882), new(4482, 9704, 14861),
+ new(3277, 7452, 11522), new(22956, 28551, 30730), new(22724, 28937, 30961),
+ new(18467, 26324, 29580), new(13234, 20713, 25649), new(11181, 17592, 22481),
+ new(8291, 18358, 24576), new(7568, 11881, 14984), new(24948, 29001, 31147),
+ new(25674, 30619, 32151), new(20841, 26793, 29603), new(14669, 24356, 28666),
+ new(11334, 23593, 28219), new(8922, 14762, 22873), new(8301, 13544, 20535)
+ ]
+ ],
+ [
+ [
+ new(17113, 23733, 27081), new(14139, 21406, 25452), new(8552, 15002, 19776),
+ new(5871, 11120, 15378), new(4455, 8616, 12253), new(3469, 6910, 10386),
+ new(2255, 4553, 6782), new(18224, 24376, 27053), new(19290, 26710, 29614),
+ new(14936, 22991, 27184), new(11238, 18951, 23762), new(8786, 15617, 20588),
+ new(7317, 13228, 18003), new(5101, 9512, 13493), new(22639, 28222, 30210),
+ new(23216, 29331, 31307), new(19075, 26762, 29895), new(15014, 23113, 27457),
+ new(11938, 19857, 24752), new(9942, 17280, 22282), new(7167, 13144, 17752)
+ ],
+ [
+ new(15820, 22738, 26488), new(13530, 20885, 25216), new(8395, 15530, 20452),
+ new(6574, 12321, 16380), new(5353, 10419, 14568), new(4613, 8446, 12381),
+ new(3440, 7158, 9903), new(24247, 29051, 31224), new(22118, 28058, 30369),
+ new(16498, 24768, 28389), new(12920, 21175, 26137), new(10730, 18619, 25352),
+ new(10187, 16279, 22791), new(9310, 14631, 22127), new(24970, 30558, 32057),
+ new(24801, 29942, 31698), new(22432, 28453, 30855), new(19054, 25680, 29580),
+ new(14392, 23036, 28109), new(12495, 20947, 26650), new(12442, 20326, 26214)
+ ]
+ ],
+ [
+ [
+ new(12162, 18785, 22648), new(12749, 19697, 23806), new(8580, 15297, 20346),
+ new(6169, 11749, 16543), new(4836, 9391, 13448), new(3821, 7711, 11613),
+ new(2228, 4601, 7070), new(16319, 24725, 28280), new(15698, 23277, 27168),
+ new(12726, 20368, 25047), new(9912, 17015, 21976), new(7888, 14220, 19179),
+ new(6777, 12284, 17018), new(4492, 8590, 12252), new(23249, 28904, 30947),
+ new(21050, 27908, 30512), new(17440, 25340, 28949), new(14059, 22018, 26541),
+ new(11288, 18903, 23898), new(9411, 16342, 21428), new(6278, 11588, 15944)
+ ],
+ [
+ new(13981, 20067, 23226), new(16922, 23580, 26783), new(11005, 19039, 24487),
+ new(7389, 14218, 19798), new(5598, 11505, 17206), new(6090, 11213, 15659),
+ new(3820, 7371, 10119), new(21082, 26925, 29675), new(21262, 28627, 31128),
+ new(18392, 26454, 30437), new(14870, 22910, 27096), new(12620, 19484, 24908),
+ new(9290, 16553, 22802), new(6668, 14288, 20004), new(27704, 31055, 31949),
+ new(24709, 29978, 31788), new(21668, 29264, 31657), new(18295, 26968, 30074),
+ new(16399, 24422, 29313), new(14347, 23026, 28104), new(12370, 19806, 24477)
+ ]
+ ],
+ [
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ]
+ ]
+ ];
+
+ private static Av1Distribution[][][][] CoefficientsBase =>
+ [
+ [
+ [
+ [
+ new(4034, 8930, 12727), new(18082, 29741, 31877), new(12596, 26124, 30493),
+ new(9446, 21118, 27005), new(6308, 15141, 21279), new(2463, 6357, 9783),
+ new(20667, 30546, 31929), new(13043, 26123, 30134), new(8151, 18757, 24778),
+ new(5255, 12839, 18632), new(2820, 7206, 11161), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(15736, 27553, 30604), new(11210, 23794, 28787), new(5947, 13874, 19701),
+ new(4215, 9323, 13891), new(2833, 6462, 10059), new(19605, 30393, 31582),
+ new(13523, 26252, 30248), new(8446, 18622, 24512), new(3818, 10343, 15974),
+ new(1481, 4117, 6796), new(22649, 31302, 32190), new(14829, 27127, 30449),
+ new(8313, 17702, 23304), new(3022, 8301, 12786), new(1536, 4412, 7184),
+ new(22354, 29774, 31372), new(14723, 25472, 29214), new(6673, 13745, 18662),
+ new(2068, 5766, 9322), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(6302, 16444, 21761), new(23040, 31538, 32475), new(15196, 28452, 31496),
+ new(10020, 22946, 28514), new(6533, 16862, 23501), new(3538, 9816, 15076),
+ new(24444, 31875, 32525), new(15881, 28924, 31635), new(9922, 22873, 28466),
+ new(6527, 16966, 23691), new(4114, 11303, 17220), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(20201, 30770, 32209), new(14754, 28071, 31258), new(8378, 20186, 26517),
+ new(5916, 15299, 21978), new(4268, 11583, 17901), new(24361, 32025, 32581),
+ new(18673, 30105, 31943), new(10196, 22244, 27576), new(5495, 14349, 20417),
+ new(2676, 7415, 11498), new(24678, 31958, 32585), new(18629, 29906, 31831),
+ new(9364, 20724, 26315), new(4641, 12318, 18094), new(2758, 7387, 11579),
+ new(25433, 31842, 32469), new(18795, 29289, 31411), new(7644, 17584, 23592),
+ new(3408, 9014, 15047), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(4536, 10072, 14001), new(25459, 31416, 32206), new(16605, 28048, 30818),
+ new(11008, 22857, 27719), new(6915, 16268, 22315), new(2625, 6812, 10537),
+ new(24257, 31788, 32499), new(16880, 29454, 31879), new(11958, 25054, 29778),
+ new(7916, 18718, 25084), new(3383, 8777, 13446), new(22720, 31603, 32393),
+ new(14960, 28125, 31335), new(9731, 22210, 27928), new(6304, 15832, 22277),
+ new(2910, 7818, 12166), new(20375, 30627, 32131), new(13904, 27284, 30887),
+ new(9368, 21558, 27144), new(5937, 14966, 21119), new(2667, 7225, 11319),
+ new(23970, 31470, 32378), new(17173, 29734, 32018), new(12795, 25441, 29965),
+ new(8981, 19680, 25893), new(4728, 11372, 16902), new(24287, 31797, 32439),
+ new(16703, 29145, 31696), new(10833, 23554, 28725), new(6468, 16566, 23057),
+ new(2415, 6562, 10278), new(26610, 32395, 32659), new(18590, 30498, 32117),
+ new(12420, 25756, 29950), new(7639, 18746, 24710), new(3001, 8086, 12347),
+ new(25076, 32064, 32580), new(17946, 30128, 32028), new(12024, 24985, 29378),
+ new(7517, 18390, 24304), new(3243, 8781, 13331), new(8192, 16384, 24576)
+ ],
+ [
+ new(6037, 16771, 21957), new(24774, 31704, 32426), new(16830, 28589, 31056),
+ new(10602, 22828, 27760), new(6733, 16829, 23071), new(3250, 8914, 13556),
+ new(25582, 32220, 32668), new(18659, 30342, 32223), new(12546, 26149, 30515),
+ new(8420, 20451, 26801), new(4636, 12420, 18344), new(27581, 32362, 32639),
+ new(18987, 30083, 31978), new(11327, 24248, 29084), new(7264, 17719, 24120),
+ new(3995, 10768, 16169), new(25893, 31831, 32487), new(16577, 28587, 31379),
+ new(10189, 22748, 28182), new(6832, 17094, 23556), new(3708, 10110, 15334),
+ new(25904, 32282, 32656), new(19721, 30792, 32276), new(12819, 26243, 30411),
+ new(8572, 20614, 26891), new(5364, 14059, 20467), new(26580, 32438, 32677),
+ new(20852, 31225, 32340), new(12435, 25700, 29967), new(8691, 20825, 26976),
+ new(4446, 12209, 17269), new(27350, 32429, 32696), new(21372, 30977, 32272),
+ new(12673, 25270, 29853), new(9208, 20925, 26640), new(5018, 13351, 18732),
+ new(27351, 32479, 32713), new(21398, 31209, 32387), new(12162, 25047, 29842),
+ new(7896, 18691, 25319), new(4670, 12882, 18881), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(5487, 10460, 13708), new(21597, 28303, 30674), new(11037, 21953, 26476),
+ new(8147, 17962, 22952), new(5242, 13061, 18532), new(1889, 5208, 8182),
+ new(26774, 32133, 32590), new(17844, 29564, 31767), new(11690, 24438, 29171),
+ new(7542, 18215, 24459), new(2993, 8050, 12319), new(28023, 32328, 32591),
+ new(18651, 30126, 31954), new(12164, 25146, 29589), new(7762, 18530, 24771),
+ new(3492, 9183, 13920), new(27591, 32008, 32491), new(17149, 28853, 31510),
+ new(11485, 24003, 28860), new(7697, 18086, 24210), new(3075, 7999, 12218),
+ new(28268, 32482, 32654), new(19631, 31051, 32404), new(13860, 27260, 31020),
+ new(9605, 21613, 27594), new(4876, 12162, 17908), new(27248, 32316, 32576),
+ new(18955, 30457, 32075), new(11824, 23997, 28795), new(7346, 18196, 24647),
+ new(3403, 9247, 14111), new(29711, 32655, 32735), new(21169, 31394, 32417),
+ new(13487, 27198, 30957), new(8828, 21683, 27614), new(4270, 11451, 17038),
+ new(28708, 32578, 32731), new(20120, 31241, 32482), new(13692, 27550, 31321),
+ new(9418, 22514, 28439), new(4999, 13283, 19462), new(8192, 16384, 24576)
+ ],
+ [
+ new(5673, 14302, 19711), new(26251, 30701, 31834), new(12782, 23783, 27803),
+ new(9127, 20657, 25808), new(6368, 16208, 21462), new(2465, 7177, 10822),
+ new(29961, 32563, 32719), new(18318, 29891, 31949), new(11361, 24514, 29357),
+ new(7900, 19603, 25607), new(4002, 10590, 15546), new(29637, 32310, 32595),
+ new(18296, 29913, 31809), new(10144, 21515, 26871), new(5358, 14322, 20394),
+ new(3067, 8362, 13346), new(28652, 32470, 32676), new(17538, 30771, 32209),
+ new(13924, 26882, 30494), new(10496, 22837, 27869), new(7236, 16396, 21621),
+ new(30743, 32687, 32746), new(23006, 31676, 32489), new(14494, 27828, 31120),
+ new(10174, 22801, 28352), new(6242, 15281, 21043), new(25817, 32243, 32720),
+ new(18618, 31367, 32325), new(13997, 28318, 31878), new(12255, 26534, 31383),
+ new(9561, 21588, 28450), new(28188, 32635, 32724), new(22060, 32365, 32728),
+ new(18102, 30690, 32528), new(14196, 28864, 31999), new(12262, 25792, 30865),
+ new(24176, 32109, 32628), new(18280, 29681, 31963), new(10205, 23703, 29664),
+ new(7889, 20025, 27676), new(6060, 16743, 23970), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(5141, 7096, 8260), new(27186, 29022, 29789), new(6668, 12568, 15682),
+ new(2172, 6181, 8638), new(1126, 3379, 4531), new(443, 1361, 2254),
+ new(26083, 31153, 32436), new(13486, 24603, 28483), new(6508, 14840, 19910),
+ new(3386, 8800, 13286), new(1530, 4322, 7054), new(29639, 32080, 32548),
+ new(15897, 27552, 30290), new(8588, 20047, 25383), new(4889, 13339, 19269),
+ new(2240, 6871, 10498), new(28165, 32197, 32517), new(20735, 30427, 31568),
+ new(14325, 24671, 27692), new(5119, 12554, 17805), new(1810, 5441, 8261),
+ new(31212, 32724, 32748), new(23352, 31766, 32545), new(14669, 27570, 31059),
+ new(8492, 20894, 27272), new(3644, 10194, 15204), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(2461, 7013, 9371), new(24749, 29600, 30986), new(9466, 19037, 22417),
+ new(3584, 9280, 14400), new(1505, 3929, 5433), new(677, 1500, 2736),
+ new(23987, 30702, 32117), new(13554, 24571, 29263), new(6211, 14556, 21155),
+ new(3135, 10972, 15625), new(2435, 7127, 11427), new(31300, 32532, 32550),
+ new(14757, 30365, 31954), new(4405, 11612, 18553), new(580, 4132, 7322),
+ new(1695, 10169, 14124), new(30008, 32282, 32591), new(19244, 30108, 31748),
+ new(11180, 24158, 29555), new(5650, 14972, 19209), new(2114, 5109, 8456),
+ new(31856, 32716, 32748), new(23012, 31664, 32572), new(13694, 26656, 30636),
+ new(8142, 19508, 26093), new(4253, 10955, 16724), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(601, 983, 1311), new(18725, 23406, 28087), new(5461, 8192, 10923),
+ new(3781, 15124, 21425), new(2587, 7761, 12072), new(106, 458, 810),
+ new(22282, 29710, 31894), new(8508, 20926, 25984), new(3726, 12713, 18083),
+ new(1620, 7112, 10893), new(729, 2236, 3495), new(30163, 32474, 32684),
+ new(18304, 30464, 32000), new(11443, 26526, 29647), new(6007, 15292, 21299),
+ new(2234, 6703, 8937), new(30954, 32177, 32571), new(17363, 29562, 31076),
+ new(9686, 22464, 27410), new(8192, 16384, 21390), new(1755, 8046, 11264),
+ new(31168, 32734, 32748), new(22486, 31441, 32471), new(12833, 25627, 29738),
+ new(6980, 17379, 23122), new(3111, 8887, 13479), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ new(6041, 11854, 15927), new(20326, 30905, 32251), new(14164, 26831, 30725),
+ new(9760, 20647, 26585), new(6416, 14953, 21219), new(2966, 7151, 10891),
+ new(23567, 31374, 32254), new(14978, 27416, 30946), new(9434, 20225, 26254),
+ new(6658, 14558, 20535), new(3916, 8677, 12989), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(18088, 29545, 31587), new(13062, 25843, 30073), new(8940, 16827, 22251),
+ new(7654, 13220, 17973), new(5733, 10316, 14456), new(22879, 31388, 32114),
+ new(15215, 27993, 30955), new(9397, 19445, 24978), new(3442, 9813, 15344),
+ new(1368, 3936, 6532), new(25494, 32033, 32406), new(16772, 27963, 30718),
+ new(9419, 18165, 23260), new(2677, 7501, 11797), new(1516, 4344, 7170),
+ new(26556, 31454, 32101), new(17128, 27035, 30108), new(8324, 15344, 20249),
+ new(1903, 5696, 9469), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(8455, 19003, 24368), new(23563, 32021, 32604), new(16237, 29446, 31935),
+ new(10724, 23999, 29358), new(6725, 17528, 24416), new(3927, 10927, 16825),
+ new(26313, 32288, 32634), new(17430, 30095, 32095), new(11116, 24606, 29679),
+ new(7195, 18384, 25269), new(4726, 12852, 19315), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(22822, 31648, 32483), new(16724, 29633, 31929), new(10261, 23033, 28725),
+ new(7029, 17840, 24528), new(4867, 13886, 21502), new(25298, 31892, 32491),
+ new(17809, 29330, 31512), new(9668, 21329, 26579), new(4774, 12956, 18976),
+ new(2322, 7030, 11540), new(25472, 31920, 32543), new(17957, 29387, 31632),
+ new(9196, 20593, 26400), new(4680, 12705, 19202), new(2917, 8456, 13436),
+ new(26471, 32059, 32574), new(18458, 29783, 31909), new(8400, 19464, 25956),
+ new(3812, 10973, 17206), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(6779, 13743, 17678), new(24806, 31797, 32457), new(17616, 29047, 31372),
+ new(11063, 23175, 28003), new(6521, 16110, 22324), new(2764, 7504, 11654),
+ new(25266, 32367, 32637), new(19054, 30553, 32175), new(12139, 25212, 29807),
+ new(7311, 18162, 24704), new(3397, 9164, 14074), new(25988, 32208, 32522),
+ new(16253, 28912, 31526), new(9151, 21387, 27372), new(5688, 14915, 21496),
+ new(2717, 7627, 12004), new(23144, 31855, 32443), new(16070, 28491, 31325),
+ new(8702, 20467, 26517), new(5243, 13956, 20367), new(2621, 7335, 11567),
+ new(26636, 32340, 32630), new(19990, 31050, 32341), new(13243, 26105, 30315),
+ new(8588, 19521, 25918), new(4717, 11585, 17304), new(25844, 32292, 32582),
+ new(19090, 30635, 32097), new(11963, 24546, 28939), new(6218, 16087, 22354),
+ new(2340, 6608, 10426), new(28046, 32576, 32694), new(21178, 31313, 32296),
+ new(13486, 26184, 29870), new(7149, 17871, 23723), new(2833, 7958, 12259),
+ new(27710, 32528, 32686), new(20674, 31076, 32268), new(12413, 24955, 29243),
+ new(6676, 16927, 23097), new(2966, 8333, 12919), new(8192, 16384, 24576)
+ ],
+ [
+ new(8639, 19339, 24429), new(24404, 31837, 32525), new(16997, 29425, 31784),
+ new(11253, 24234, 29149), new(6751, 17394, 24028), new(3490, 9830, 15191),
+ new(26283, 32471, 32714), new(19599, 31168, 32442), new(13146, 26954, 30893),
+ new(8214, 20588, 26890), new(4699, 13081, 19300), new(28212, 32458, 32669),
+ new(18594, 30316, 32100), new(11219, 24408, 29234), new(6865, 17656, 24149),
+ new(3678, 10362, 16006), new(25825, 32136, 32616), new(17313, 29853, 32021),
+ new(11197, 24471, 29472), new(6947, 17781, 24405), new(3768, 10660, 16261),
+ new(27352, 32500, 32706), new(20850, 31468, 32469), new(14021, 27707, 31133),
+ new(8964, 21748, 27838), new(5437, 14665, 21187), new(26304, 32492, 32698),
+ new(20409, 31380, 32385), new(13682, 27222, 30632), new(8974, 21236, 26685),
+ new(4234, 11665, 16934), new(26273, 32357, 32711), new(20672, 31242, 32441),
+ new(14172, 27254, 30902), new(9870, 21898, 27275), new(5164, 13506, 19270),
+ new(26725, 32459, 32728), new(20991, 31442, 32527), new(13071, 26434, 30811),
+ new(8184, 20090, 26742), new(4803, 13255, 19895), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(7555, 14942, 18501), new(24410, 31178, 32287), new(14394, 26738, 30253),
+ new(8413, 19554, 25195), new(4766, 12924, 18785), new(2029, 5806, 9207),
+ new(26776, 32364, 32663), new(18732, 29967, 31931), new(11005, 23786, 28852),
+ new(6466, 16909, 23510), new(3044, 8638, 13419), new(29208, 32582, 32704),
+ new(20068, 30857, 32208), new(12003, 25085, 29595), new(6947, 17750, 24189),
+ new(3245, 9103, 14007), new(27359, 32465, 32669), new(19421, 30614, 32174),
+ new(11915, 25010, 29579), new(6950, 17676, 24074), new(3007, 8473, 13096),
+ new(29002, 32676, 32735), new(22102, 31849, 32576), new(14408, 28009, 31405),
+ new(9027, 21679, 27931), new(4694, 12678, 18748), new(28216, 32528, 32682),
+ new(20849, 31264, 32318), new(12756, 25815, 29751), new(7565, 18801, 24923),
+ new(3509, 9533, 14477), new(30133, 32687, 32739), new(23063, 31910, 32515),
+ new(14588, 28051, 31132), new(9085, 21649, 27457), new(4261, 11654, 17264),
+ new(29518, 32691, 32748), new(22451, 31959, 32613), new(14864, 28722, 31700),
+ new(9695, 22964, 28716), new(4932, 13358, 19502), new(8192, 16384, 24576)
+ ],
+ [
+ new(6465, 16958, 21688), new(25199, 31514, 32360), new(14774, 27149, 30607),
+ new(9257, 21438, 26972), new(5723, 15183, 21882), new(3150, 8879, 13731),
+ new(26989, 32262, 32682), new(17396, 29937, 32085), new(11387, 24901, 29784),
+ new(7289, 18821, 25548), new(3734, 10577, 16086), new(29728, 32501, 32695),
+ new(17431, 29701, 31903), new(9921, 22826, 28300), new(5896, 15434, 22068),
+ new(3430, 9646, 14757), new(28614, 32511, 32705), new(19364, 30638, 32263),
+ new(13129, 26254, 30402), new(8754, 20484, 26440), new(4378, 11607, 17110),
+ new(30292, 32671, 32744), new(21780, 31603, 32501), new(14314, 27829, 31291),
+ new(9611, 22327, 28263), new(4890, 13087, 19065), new(25862, 32567, 32733),
+ new(20794, 32050, 32567), new(17243, 30625, 32254), new(13283, 27628, 31474),
+ new(9669, 22532, 28918), new(27435, 32697, 32748), new(24922, 32390, 32714),
+ new(21449, 31504, 32536), new(16392, 29729, 31832), new(11692, 24884, 29076),
+ new(24193, 32290, 32735), new(18909, 31104, 32563), new(12236, 26841, 31403),
+ new(8171, 21840, 29082), new(7224, 17280, 25275), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(3078, 6839, 9890), new(13837, 20450, 24479), new(5914, 14222, 19328),
+ new(3866, 10267, 14762), new(2612, 7208, 11042), new(1067, 2991, 4776),
+ new(25817, 31646, 32529), new(13708, 26338, 30385), new(7328, 18585, 24870),
+ new(4691, 13080, 19276), new(1825, 5253, 8352), new(29386, 32315, 32624),
+ new(17160, 29001, 31360), new(9602, 21862, 27396), new(5915, 15772, 22148),
+ new(2786, 7779, 12047), new(29246, 32450, 32663), new(18696, 29929, 31818),
+ new(10510, 23369, 28560), new(6229, 16499, 23125), new(2608, 7448, 11705),
+ new(30753, 32710, 32748), new(21638, 31487, 32503), new(12937, 26854, 30870),
+ new(8182, 20596, 26970), new(3637, 10269, 15497), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(5244, 12150, 16906), new(20486, 26858, 29701), new(7756, 18317, 23735),
+ new(3452, 9256, 13146), new(2020, 5206, 8229), new(1801, 4993, 7903),
+ new(27051, 31858, 32531), new(15988, 27531, 30619), new(9188, 21484, 26719),
+ new(6273, 17186, 23800), new(3108, 9355, 14764), new(31076, 32520, 32680),
+ new(18119, 30037, 31850), new(10244, 22969, 27472), new(4692, 14077, 19273),
+ new(3694, 11677, 17556), new(30060, 32581, 32720), new(21011, 30775, 32120),
+ new(11931, 24820, 29289), new(7119, 17662, 24356), new(3833, 10706, 16304),
+ new(31954, 32731, 32748), new(23913, 31724, 32489), new(15520, 28060, 31286),
+ new(11517, 23008, 28571), new(6193, 14508, 20629), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(1035, 2807, 4156), new(13162, 18138, 20939), new(2696, 6633, 8755),
+ new(1373, 4161, 6853), new(1099, 2746, 4716), new(340, 1021, 1599),
+ new(22826, 30419, 32135), new(10395, 21762, 26942), new(4726, 12407, 17361),
+ new(2447, 7080, 10593), new(1227, 3717, 6011), new(28156, 31424, 31934),
+ new(16915, 27754, 30373), new(9148, 20990, 26431), new(5950, 15515, 21148),
+ new(2492, 7327, 11526), new(30602, 32477, 32670), new(20026, 29955, 31568),
+ new(11220, 23628, 28105), new(6652, 17019, 22973), new(3064, 8536, 13043),
+ new(31769, 32724, 32748), new(22230, 30887, 32373), new(12234, 25079, 29731),
+ new(7326, 18816, 25353), new(3933, 10907, 16616), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ new(8896, 16227, 20630), new(23629, 31782, 32527), new(15173, 27755, 31321),
+ new(10158, 21233, 27382), new(6420, 14857, 21558), new(3269, 8155, 12646),
+ new(24835, 32009, 32496), new(16509, 28421, 31579), new(10957, 21514, 27418),
+ new(7881, 15930, 22096), new(5388, 10960, 15918), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(20745, 30773, 32093), new(15200, 27221, 30861), new(13032, 20873, 25667),
+ new(12285, 18663, 23494), new(11563, 17481, 21489), new(26260, 31982, 32320),
+ new(15397, 28083, 31100), new(9742, 19217, 24824), new(3261, 9629, 15362),
+ new(1480, 4322, 7499), new(27599, 32256, 32460), new(16857, 27659, 30774),
+ new(9551, 18290, 23748), new(3052, 8933, 14103), new(2021, 5910, 9787),
+ new(29005, 32015, 32392), new(17677, 27694, 30863), new(9204, 17356, 23219),
+ new(2403, 7516, 12814), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(10808, 22056, 26896), new(25739, 32313, 32676), new(17288, 30203, 32221),
+ new(11359, 24878, 29896), new(6949, 17767, 24893), new(4287, 11796, 18071),
+ new(27880, 32521, 32705), new(19038, 31004, 32414), new(12564, 26345, 30768),
+ new(8269, 19947, 26779), new(5674, 14657, 21674), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(25742, 32319, 32671), new(19557, 31164, 32454), new(13381, 26381, 30755),
+ new(10101, 21466, 26722), new(9209, 19650, 26825), new(27107, 31917, 32432),
+ new(18056, 28893, 31203), new(10200, 21434, 26764), new(4660, 12913, 19502),
+ new(2368, 6930, 12504), new(26960, 32158, 32613), new(18628, 30005, 32031),
+ new(10233, 22442, 28232), new(5471, 14630, 21516), new(3235, 10767, 17109),
+ new(27696, 32440, 32692), new(20032, 31167, 32438), new(8700, 21341, 28442),
+ new(5662, 14831, 21795), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(9704, 17294, 21132), new(26762, 32278, 32633), new(18382, 29620, 31819),
+ new(10891, 23475, 28723), new(6358, 16583, 23309), new(3248, 9118, 14141),
+ new(27204, 32573, 32699), new(19818, 30824, 32329), new(11772, 25120, 30041),
+ new(6995, 18033, 25039), new(3752, 10442, 16098), new(27222, 32256, 32559),
+ new(15356, 28399, 31475), new(8821, 20635, 27057), new(5511, 14404, 21239),
+ new(2935, 8222, 13051), new(24875, 32120, 32529), new(15233, 28265, 31445),
+ new(8605, 20570, 26932), new(5431, 14413, 21196), new(2994, 8341, 13223),
+ new(28201, 32604, 32700), new(21041, 31446, 32456), new(13221, 26213, 30475),
+ new(8255, 19385, 26037), new(4930, 12585, 18830), new(28768, 32448, 32627),
+ new(19705, 30561, 32021), new(11572, 23589, 28220), new(5532, 15034, 21446),
+ new(2460, 7150, 11456), new(29874, 32619, 32699), new(21621, 31071, 32201),
+ new(12511, 24747, 28992), new(6281, 16395, 22748), new(3246, 9278, 14497),
+ new(29715, 32625, 32712), new(20958, 31011, 32283), new(11233, 23671, 28806),
+ new(6012, 16128, 22868), new(3427, 9851, 15414), new(8192, 16384, 24576)
+ ],
+ [
+ new(11016, 22111, 26794), new(25946, 32357, 32677), new(17890, 30452, 32252),
+ new(11678, 25142, 29816), new(6720, 17534, 24584), new(4230, 11665, 17820),
+ new(28400, 32623, 32747), new(21164, 31668, 32575), new(13572, 27388, 31182),
+ new(8234, 20750, 27358), new(5065, 14055, 20897), new(28981, 32547, 32705),
+ new(18681, 30543, 32239), new(10919, 24075, 29286), new(6431, 17199, 24077),
+ new(3819, 10464, 16618), new(26870, 32467, 32693), new(19041, 30831, 32347),
+ new(11794, 25211, 30016), new(6888, 18019, 24970), new(4370, 12363, 18992),
+ new(29578, 32670, 32744), new(23159, 32007, 32613), new(15315, 28669, 31676),
+ new(9298, 22607, 28782), new(6144, 15913, 22968), new(28110, 32499, 32669),
+ new(21574, 30937, 32015), new(12759, 24818, 28727), new(6545, 16761, 23042),
+ new(3649, 10597, 16833), new(28163, 32552, 32728), new(22101, 31469, 32464),
+ new(13160, 25472, 30143), new(7303, 18684, 25468), new(5241, 13975, 20955),
+ new(28400, 32631, 32744), new(22104, 31793, 32603), new(13557, 26571, 30846),
+ new(7749, 19861, 26675), new(4873, 14030, 21234), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(9800, 17635, 21073), new(26153, 31885, 32527), new(15038, 27852, 31006),
+ new(8718, 20564, 26486), new(5128, 14076, 20514), new(2636, 7566, 11925),
+ new(27551, 32504, 32701), new(18310, 30054, 32100), new(10211, 23420, 29082),
+ new(6222, 16876, 23916), new(3462, 9954, 15498), new(29991, 32633, 32721),
+ new(19883, 30751, 32201), new(11141, 24184, 29285), new(6420, 16940, 23774),
+ new(3392, 9753, 15118), new(28465, 32616, 32712), new(19850, 30702, 32244),
+ new(10983, 24024, 29223), new(6294, 16770, 23582), new(3244, 9283, 14509),
+ new(30023, 32717, 32748), new(22940, 32032, 32626), new(14282, 27928, 31473),
+ new(8562, 21327, 27914), new(4846, 13393, 19919), new(29981, 32590, 32695),
+ new(20465, 30963, 32166), new(11479, 23579, 28195), new(5916, 15648, 22073),
+ new(3031, 8605, 13398), new(31146, 32691, 32739), new(23106, 31724, 32444),
+ new(13783, 26738, 30439), new(7852, 19468, 25807), new(3860, 11124, 16853),
+ new(31014, 32724, 32748), new(23629, 32109, 32628), new(14747, 28115, 31403),
+ new(8545, 21242, 27478), new(4574, 12781, 19067), new(8192, 16384, 24576)
+ ],
+ [
+ new(9185, 19694, 24688), new(26081, 31985, 32621), new(16015, 29000, 31787),
+ new(10542, 23690, 29206), new(6732, 17945, 24677), new(3916, 11039, 16722),
+ new(28224, 32566, 32744), new(19100, 31138, 32485), new(12528, 26620, 30879),
+ new(7741, 20277, 26885), new(4566, 12845, 18990), new(29933, 32593, 32718),
+ new(17670, 30333, 32155), new(10385, 23600, 28909), new(6243, 16236, 22407),
+ new(3976, 10389, 16017), new(28377, 32561, 32738), new(19366, 31175, 32482),
+ new(13327, 27175, 31094), new(8258, 20769, 27143), new(4703, 13198, 19527),
+ new(31086, 32706, 32748), new(22853, 31902, 32583), new(14759, 28186, 31419),
+ new(9284, 22382, 28348), new(5585, 15192, 21868), new(28291, 32652, 32746),
+ new(19849, 32107, 32571), new(14834, 26818, 29214), new(10306, 22594, 28672),
+ new(6615, 17384, 23384), new(28947, 32604, 32745), new(25625, 32289, 32646),
+ new(18758, 28672, 31403), new(10017, 23430, 28523), new(6862, 15269, 22131),
+ new(23933, 32509, 32739), new(19927, 31495, 32631), new(11903, 26023, 30621),
+ new(7026, 20094, 27252), new(5998, 18106, 24437), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(4456, 11274, 15533), new(21219, 29079, 31616), new(11173, 23774, 28567),
+ new(7282, 18293, 24263), new(4890, 13286, 19115), new(1890, 5508, 8659),
+ new(26651, 32136, 32647), new(14630, 28254, 31455), new(8716, 21287, 27395),
+ new(5615, 15331, 22008), new(2675, 7700, 12150), new(29954, 32526, 32690),
+ new(16126, 28982, 31633), new(9030, 21361, 27352), new(5411, 14793, 21271),
+ new(2943, 8422, 13163), new(29539, 32601, 32730), new(18125, 30385, 32201),
+ new(10422, 24090, 29468), new(6468, 17487, 24438), new(2970, 8653, 13531),
+ new(30912, 32715, 32748), new(20666, 31373, 32497), new(12509, 26640, 30917),
+ new(8058, 20629, 27290), new(4231, 12006, 18052), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(10202, 20633, 25484), new(27336, 31445, 32352), new(12420, 24384, 28552),
+ new(7648, 18115, 23856), new(5662, 14341, 19902), new(3611, 10328, 15390),
+ new(30945, 32616, 32736), new(18682, 30505, 32253), new(11513, 25336, 30203),
+ new(7449, 19452, 26148), new(4482, 13051, 18886), new(32022, 32690, 32747),
+ new(18578, 30501, 32146), new(11249, 23368, 28631), new(5645, 16958, 22158),
+ new(5009, 11444, 16637), new(31357, 32710, 32748), new(21552, 31494, 32504),
+ new(13891, 27677, 31340), new(9051, 22098, 28172), new(5190, 13377, 19486),
+ new(32364, 32740, 32748), new(24839, 31907, 32551), new(17160, 28779, 31696),
+ new(12452, 24137, 29602), new(6165, 15389, 22477), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(2575, 7281, 11077), new(14002, 20866, 25402), new(6343, 15056, 19658),
+ new(4474, 11858, 17041), new(2865, 8299, 12534), new(1344, 3949, 6391),
+ new(24720, 31239, 32459), new(12585, 25356, 29968), new(7181, 18246, 24444),
+ new(5025, 13667, 19885), new(2521, 7304, 11605), new(29908, 32252, 32584),
+ new(17421, 29156, 31575), new(9889, 22188, 27782), new(5878, 15647, 22123),
+ new(2814, 8665, 13323), new(30183, 32568, 32713), new(18528, 30195, 32049),
+ new(10982, 24606, 29657), new(6957, 18165, 25231), new(3508, 10118, 15468),
+ new(31761, 32736, 32748), new(21041, 31328, 32546), new(12568, 26732, 31166),
+ new(8052, 20720, 27733), new(4336, 12192, 18396), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ new(7062, 16472, 22319), new(24538, 32261, 32674), new(13675, 28041, 31779),
+ new(8590, 20674, 27631), new(5685, 14675, 22013), new(3655, 9898, 15731),
+ new(26493, 32418, 32658), new(16376, 29342, 32090), new(10594, 22649, 28970),
+ new(8176, 17170, 24303), new(5605, 12694, 19139), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(23888, 31902, 32542), new(18612, 29687, 31987), new(16245, 24852, 29249),
+ new(15765, 22608, 27559), new(19895, 24699, 27510), new(28401, 32212, 32457),
+ new(15274, 27825, 30980), new(9364, 18128, 24332), new(2283, 8193, 15082),
+ new(1228, 3972, 7881), new(29455, 32469, 32620), new(17981, 28245, 31388),
+ new(10921, 20098, 26240), new(3743, 11829, 18657), new(2374, 9593, 15715),
+ new(31068, 32466, 32635), new(20321, 29572, 31971), new(10771, 20255, 27119),
+ new(2795, 10410, 17361), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(9320, 22102, 27840), new(27057, 32464, 32724), new(16331, 30268, 32309),
+ new(10319, 23935, 29720), new(6189, 16448, 24106), new(3589, 10884, 18808),
+ new(29026, 32624, 32748), new(19226, 31507, 32587), new(12692, 26921, 31203),
+ new(7049, 19532, 27635), new(7727, 15669, 23252), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(28056, 32625, 32748), new(22383, 32075, 32669), new(15417, 27098, 31749),
+ new(18127, 26493, 27190), new(5461, 16384, 21845), new(27982, 32091, 32584),
+ new(19045, 29868, 31972), new(10397, 22266, 27932), new(5990, 13697, 21500),
+ new(1792, 6912, 15104), new(28198, 32501, 32718), new(21534, 31521, 32569),
+ new(11109, 25217, 30017), new(5671, 15124, 26151), new(4681, 14043, 18725),
+ new(28688, 32580, 32741), new(22576, 32079, 32661), new(10627, 22141, 28340),
+ new(9362, 14043, 28087), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(7754, 16948, 22142), new(25670, 32330, 32691), new(15663, 29225, 31994),
+ new(9878, 23288, 29158), new(6419, 17088, 24336), new(3859, 11003, 17039),
+ new(27562, 32595, 32725), new(17575, 30588, 32399), new(10819, 24838, 30309),
+ new(7124, 18686, 25916), new(4479, 12688, 19340), new(28385, 32476, 32673),
+ new(15306, 29005, 31938), new(8937, 21615, 28322), new(5982, 15603, 22786),
+ new(3620, 10267, 16136), new(27280, 32464, 32667), new(15607, 29160, 32004),
+ new(9091, 22135, 28740), new(6232, 16632, 24020), new(4047, 11377, 17672),
+ new(29220, 32630, 32718), new(19650, 31220, 32462), new(13050, 26312, 30827),
+ new(9228, 20870, 27468), new(6146, 15149, 21971), new(30169, 32481, 32623),
+ new(17212, 29311, 31554), new(9911, 21311, 26882), new(4487, 13314, 20372),
+ new(2570, 7772, 12889), new(30924, 32613, 32708), new(19490, 30206, 32107),
+ new(11232, 23998, 29276), new(6769, 17955, 25035), new(4398, 12623, 19214),
+ new(30609, 32627, 32722), new(19370, 30582, 32287), new(10457, 23619, 29409),
+ new(6443, 17637, 24834), new(4645, 13236, 20106), new(8192, 16384, 24576)
+ ],
+ [
+ new(8626, 20271, 26216), new(26707, 32406, 32711), new(16999, 30329, 32286),
+ new(11445, 25123, 30286), new(6411, 18828, 25601), new(6801, 12458, 20248),
+ new(29918, 32682, 32748), new(20649, 31739, 32618), new(12879, 27773, 31581),
+ new(7896, 21751, 28244), new(5260, 14870, 23698), new(29252, 32593, 32731),
+ new(17072, 30460, 32294), new(10653, 24143, 29365), new(6536, 17490, 23983),
+ new(4929, 13170, 20085), new(28137, 32518, 32715), new(18171, 30784, 32407),
+ new(11437, 25436, 30459), new(7252, 18534, 26176), new(4126, 13353, 20978),
+ new(31162, 32726, 32748), new(23017, 32222, 32701), new(15629, 29233, 32046),
+ new(9387, 22621, 29480), new(6922, 17616, 25010), new(28838, 32265, 32614),
+ new(19701, 30206, 31920), new(11214, 22410, 27933), new(5320, 14177, 23034),
+ new(5049, 12881, 17827), new(27484, 32471, 32734), new(21076, 31526, 32561),
+ new(12707, 26303, 31211), new(8169, 21722, 28219), new(6045, 19406, 27042),
+ new(27753, 32572, 32745), new(20832, 31878, 32653), new(13250, 27356, 31674),
+ new(7718, 21508, 29858), new(7209, 18350, 25559), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(7876, 16901, 21741), new(24001, 31898, 32625), new(14529, 27959, 31451),
+ new(8273, 20818, 27258), new(5278, 14673, 21510), new(2983, 8843, 14039),
+ new(28016, 32574, 32732), new(17471, 30306, 32301), new(10224, 24063, 29728),
+ new(6602, 17954, 25052), new(4002, 11585, 17759), new(30190, 32634, 32739),
+ new(17497, 30282, 32270), new(10229, 23729, 29538), new(6344, 17211, 24440),
+ new(3849, 11189, 17108), new(28570, 32583, 32726), new(17521, 30161, 32238),
+ new(10153, 23565, 29378), new(6455, 17341, 24443), new(3907, 11042, 17024),
+ new(30689, 32715, 32748), new(21546, 31840, 32610), new(13547, 27581, 31459),
+ new(8912, 21757, 28309), new(5548, 15080, 22046), new(30783, 32540, 32685),
+ new(17540, 29528, 31668), new(10160, 21468, 26783), new(4724, 13393, 20054),
+ new(2702, 8174, 13102), new(31648, 32686, 32742), new(20954, 31094, 32337),
+ new(12420, 25698, 30179), new(7304, 19320, 26248), new(4366, 12261, 18864),
+ new(31581, 32723, 32748), new(21373, 31586, 32525), new(12744, 26625, 30885),
+ new(7431, 20322, 26950), new(4692, 13323, 20111), new(8192, 16384, 24576)
+ ],
+ [
+ new(7833, 18369, 24095), new(26650, 32273, 32702), new(16371, 29961, 32191),
+ new(11055, 24082, 29629), new(6892, 18644, 25400), new(5006, 13057, 19240),
+ new(29834, 32666, 32748), new(19577, 31335, 32570), new(12253, 26509, 31122),
+ new(7991, 20772, 27711), new(5677, 15910, 23059), new(30109, 32532, 32720),
+ new(16747, 30166, 32252), new(10134, 23542, 29184), new(5791, 16176, 23556),
+ new(4362, 10414, 17284), new(29492, 32626, 32748), new(19894, 31402, 32525),
+ new(12942, 27071, 30869), new(8346, 21216, 27405), new(6572, 17087, 23859),
+ new(32035, 32735, 32748), new(22957, 31838, 32618), new(14724, 28572, 31772),
+ new(10364, 23999, 29553), new(7004, 18433, 25655), new(27528, 32277, 32681),
+ new(16959, 31171, 32096), new(10486, 23593, 27962), new(8192, 16384, 23211),
+ new(8937, 17873, 20852), new(27715, 32002, 32615), new(15073, 29491, 31676),
+ new(11264, 24576, 28672), new(2341, 18725, 23406), new(7282, 18204, 25486),
+ new(28547, 32213, 32657), new(20788, 29773, 32239), new(6780, 21469, 30508),
+ new(5958, 14895, 23831), new(16384, 21845, 27307), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(5992, 14304, 19765), new(22612, 31238, 32456), new(13456, 27162, 31087),
+ new(8001, 20062, 26504), new(5168, 14105, 20764), new(2632, 7771, 12385),
+ new(27034, 32344, 32709), new(15850, 29415, 31997), new(9494, 22776, 28841),
+ new(6151, 16830, 23969), new(3461, 10039, 15722), new(30134, 32569, 32731),
+ new(15638, 29422, 31945), new(9150, 21865, 28218), new(5647, 15719, 22676),
+ new(3402, 9772, 15477), new(28530, 32586, 32735), new(17139, 30298, 32292),
+ new(10200, 24039, 29685), new(6419, 17674, 24786), new(3544, 10225, 15824),
+ new(31333, 32726, 32748), new(20618, 31487, 32544), new(12901, 27217, 31232),
+ new(8624, 21734, 28171), new(5104, 14191, 20748), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(11206, 21090, 26561), new(28759, 32279, 32671), new(14171, 27952, 31569),
+ new(9743, 22907, 29141), new(6871, 17886, 24868), new(4960, 13152, 19315),
+ new(31077, 32661, 32748), new(19400, 31195, 32515), new(12752, 26858, 31040),
+ new(8370, 22098, 28591), new(5457, 15373, 22298), new(31697, 32706, 32748),
+ new(17860, 30657, 32333), new(12510, 24812, 29261), new(6180, 19124, 24722),
+ new(5041, 13548, 17959), new(31552, 32716, 32748), new(21908, 31769, 32623),
+ new(14470, 28201, 31565), new(9493, 22982, 28608), new(6858, 17240, 24137),
+ new(32543, 32752, 32756), new(24286, 32097, 32666), new(15958, 29217, 32024),
+ new(10207, 24234, 29958), new(6929, 18305, 25652), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ],
+ [
+ [
+ new(4137, 10847, 15682), new(17824, 27001, 30058), new(10204, 22796, 28291),
+ new(6076, 15935, 22125), new(3852, 10937, 16816), new(2252, 6324, 10131),
+ new(25840, 32016, 32662), new(15109, 28268, 31531), new(9385, 22231, 28340),
+ new(6082, 16672, 23479), new(3318, 9427, 14681), new(30594, 32574, 32718),
+ new(16836, 29552, 31859), new(9556, 22542, 28356), new(6305, 16725, 23540),
+ new(3376, 9895, 15184), new(29383, 32617, 32745), new(18891, 30809, 32401),
+ new(11688, 25942, 30687), new(7468, 19469, 26651), new(3909, 11358, 17012),
+ new(31564, 32736, 32748), new(20906, 31611, 32600), new(13191, 27621, 31537),
+ new(8768, 22029, 28676), new(5079, 14109, 20906), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ],
+ [
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576),
+ new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576)
+ ]
+ ]
+ ]
+ ];
+
+ private static Av1Distribution[][][][] BaseEndOfBlock =>
+ [
+ [
+ [
+ [new(17837, 29055), new(29600, 31446), new(30844, 31878), new(24926, 28948)],
+ [new(21365, 30026), new(30512, 32423), new(31658, 32621), new(29630, 31881)]
+ ],
+ [
+ [new(5717, 26477), new(30491, 31703), new(31550, 32158), new(29648, 31491)],
+ [new(12608, 27820), new(30680, 32225), new(30809, 32335), new(31299, 32423)]
+ ],
+ [
+ [new(1786, 12612), new(30663, 31625), new(32339, 32468), new(31148, 31833)],
+ [new(18857, 23865), new(31428, 32428), new(31744, 32373), new(31775, 32526)]
+ ],
+ [
+ [new(1787, 2532), new(30832, 31662), new(31824, 32682), new(32133, 32569)],
+ [new(13751, 22235), new(32089, 32409), new(27084, 27920), new(29291, 32594)]
+ ],
+ [
+ [new(1725, 3449), new(31102, 31935), new(32457, 32613), new(32412, 32649)],
+ [new(10923, 21845), new(10923, 21845), new(10923, 21845), new(10923, 21845)]
+ ]
+ ],
+ [
+ [
+ [new(17560, 29888), new(29671, 31549), new(31007, 32056), new(27286, 30006)],
+ [new(26594, 31212), new(31208, 32582), new(31835, 32637), new(30595, 32206)]
+ ],
+ [
+ [new(15239, 29932), new(31315, 32095), new(32130, 32434), new(30864, 31996)],
+ [new(26279, 30968), new(31142, 32495), new(31713, 32540), new(31929, 32594)]
+ ],
+ [
+ [new(2644, 25198), new(32038, 32451), new(32639, 32695), new(32166, 32518)],
+ [new(17187, 27668), new(31714, 32550), new(32283, 32678), new(31930, 32563)]
+ ],
+ [
+ [new(1044, 2257), new(30755, 31923), new(32208, 32693), new(32244, 32615)],
+ [new(21317, 26207), new(29133, 30868), new(29311, 31231), new(29657, 31087)]
+ ],
+ [
+ [new(478, 1834), new(31005, 31987), new(32317, 32724), new(30865, 32648)],
+ [new(10923, 21845), new(10923, 21845), new(10923, 21845), new(10923, 21845)]
+ ]
+ ],
+ [
+ [
+ [new(20092, 30774), new(30695, 32020), new(31131, 32103), new(28666, 30870)],
+ [new(27258, 31095), new(31804, 32623), new(31763, 32528), new(31438, 32506)]
+ ],
+ [
+ [new(18049, 30489), new(31706, 32286), new(32163, 32473), new(31550, 32184)],
+ [new(27116, 30842), new(31971, 32598), new(32088, 32576), new(32067, 32664)]
+ ],
+ [
+ [new(12854, 29093), new(32272, 32558), new(32667, 32729), new(32306, 32585)],
+ [new(25476, 30366), new(32169, 32687), new(32479, 32689), new(31673, 32634)]
+ ],
+ [
+ [new(2809, 19301), new(32205, 32622), new(32338, 32730), new(31786, 32616)],
+ [new(22737, 29105), new(30810, 32362), new(30014, 32627), new(30528, 32574)]
+ ],
+ [
+ [new(935, 3382), new(30789, 31909), new(32466, 32756), new(30860, 32513)],
+ [new(10923, 21845), new(10923, 21845), new(10923, 21845), new(10923, 21845)]
+ ]
+ ],
+ [
+ [
+ [new(22497, 31198), new(31715, 32495), new(31606, 32337), new(30388, 31990)],
+ [new(27877, 31584), new(32170, 32728), new(32155, 32688), new(32219, 32702)]
+ ],
+ [
+ [new(21457, 31043), new(31951, 32483), new(32153, 32562), new(31473, 32215)],
+ [new(27558, 31151), new(32020, 32640), new(32097, 32575), new(32242, 32719)]
+ ],
+ [
+ [new(19980, 30591), new(32219, 32597), new(32581, 32706), new(31803, 32287)],
+ [new(26473, 30507), new(32431, 32723), new(32196, 32611), new(31588, 32528)]
+ ],
+ [
+ [new(24647, 30463), new(32412, 32695), new(32468, 32720), new(31269, 32523)],
+ [new(28482, 31505), new(32152, 32701), new(31732, 32598), new(31767, 32712)]
+ ],
+ [
+ [new(12358, 24977), new(31331, 32385), new(32634, 32756), new(30411, 32548)],
+ [new(10923, 21845), new(10923, 21845), new(10923, 21845), new(10923, 21845)]
+ ]
+ ]
+ ];
+
+ private static Av1Distribution[][][] DcSign =>
+ [
+ [
+ [new(128 * 125), new(128 * 102), new(128 * 147)],
+ [new(128 * 119), new(128 * 101), new(128 * 135)]
+ ],
+ [
+ [new(128 * 125), new(128 * 102), new(128 * 147)],
+ [new(128 * 119), new(128 * 101), new(128 * 135)]
+ ],
+ [
+ [new(128 * 125), new(128 * 102), new(128 * 147)],
+ [new(128 * 119), new(128 * 101), new(128 * 135)]
+ ],
+ [
+ [new(128 * 125), new(128 * 102), new(128 * 147)],
+ [new(128 * 119), new(128 * 101), new(128 * 135)]
+ ],
+ ];
+
+ private static Av1Distribution[][][] TransformBlockSkip =>
+ [
+ [
+ [
+ new(31849), new(5892), new(12112), new(21935), new(20289), new(27473), new(32487),
+ new(7654), new(19473), new(29984), new(9961), new(30242), new(32117)
+ ],
+ [
+ new(31548), new(1549), new(10130), new(16656), new(18591), new(26308), new(32537),
+ new(5403), new(18096), new(30003), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(29957), new(5391), new(18039), new(23566), new(22431), new(25822), new(32197),
+ new(3778), new(15336), new(28981), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(17920), new(1818), new(7282), new(25273), new(10923), new(31554), new(32624),
+ new(1366), new(15628), new(30462), new(146), new(5132), new(31657)
+ ],
+ [
+ new(6308), new(117), new(1638), new(2161), new(16384), new(10923), new(30247),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(30371), new(7570), new(13155), new(20751), new(20969), new(27067), new(32013),
+ new(5495), new(17942), new(28280), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(31782), new(1836), new(10689), new(17604), new(21622), new(27518), new(32399),
+ new(4419), new(16294), new(28345), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(31901), new(10311), new(18047), new(24806), new(23288), new(27914), new(32296),
+ new(4215), new(15756), new(28341), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(26726), new(1045), new(11703), new(20590), new(18554), new(25970), new(31938),
+ new(5583), new(21313), new(29390), new(641), new(22265), new(31452)
+ ],
+ [
+ new(26584), new(188), new(8847), new(24519), new(22938), new(30583), new(32608),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(29614), new(9068), new(12924), new(19538), new(17737), new(24619), new(30642),
+ new(4119), new(16026), new(25657), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(31957), new(3230), new(11153), new(18123), new(20143), new(26536), new(31986),
+ new(3050), new(14603), new(25155), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(32363), new(10692), new(19090), new(24357), new(24442), new(28312), new(32169),
+ new(3648), new(15690), new(26815), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(30669), new(3832), new(11663), new(18889), new(19782), new(23313), new(31330),
+ new(5124), new(18719), new(28468), new(3082), new(20982), new(29443)
+ ],
+ [
+ new(28573), new(3183), new(17802), new(25977), new(26677), new(27832), new(32387),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(26887), new(6729), new(10361), new(17442), new(15045), new(22478), new(29072),
+ new(2713), new(11861), new(20773), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(31903), new(2044), new(7528), new(14618), new(16182), new(24168), new(31037),
+ new(2786), new(11194), new(20155), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(32510), new(8430), new(17318), new(24154), new(23674), new(28789), new(32139),
+ new(3440), new(13117), new(22702), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(31671), new(2056), new(11746), new(16852), new(18635), new(24715), new(31484),
+ new(4656), new(16074), new(24704), new(1806), new(14645), new(25336)
+ ],
+ [
+ new(31539), new(8433), new(20576), new(27904), new(27852), new(30026), new(32441),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384)
+ ]
+ ]
+ ];
+
+ private static Av1Distribution[][][][] EndOfBlockExtra =>
+ [
+ [
+ [
+ [
+ new(16384), new(16384), new(16384), new(16961), new(17223), new(7621),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(19069), new(22525), new(13377),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(20401), new(17025), new(12845),
+ new(12873), new(14094), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(20681), new(20701), new(15250),
+ new(15017), new(14928), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(23905), new(17194), new(16170),
+ new(17695), new(13826), new(15810), new(12036), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(23959), new(20799), new(19021),
+ new(16203), new(17886), new(14144), new(12010), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(27399), new(16327), new(18071),
+ new(19584), new(20721), new(18432), new(19560), new(10150), new(8805),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(24932), new(20833), new(12027),
+ new(16670), new(19914), new(15106), new(17662), new(13783), new(28756),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(23406), new(21845), new(18432),
+ new(16384), new(17096), new(12561), new(17320), new(22395), new(21370),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ new(16384), new(16384), new(16384), new(17471), new(20223), new(11357),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(20335), new(21667), new(14818),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(20430), new(20662), new(15367),
+ new(16970), new(14657), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(22117), new(22028), new(18650),
+ new(16042), new(15885), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(22409), new(21012), new(15650),
+ new(17395), new(15469), new(20205), new(19511), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(24220), new(22480), new(17737),
+ new(18916), new(19268), new(18412), new(18844), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(25991), new(20314), new(17731),
+ new(19678), new(18649), new(17307), new(21798), new(17549), new(15630),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(26585), new(21469), new(20432),
+ new(17735), new(19280), new(15235), new(20297), new(22471), new(28997),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(26605), new(11304), new(16726),
+ new(16560), new(20866), new(23524), new(19878), new(13469), new(23084),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ new(16384), new(16384), new(16384), new(18983), new(20512), new(14885),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(20090), new(19444), new(17286),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(19139), new(21487), new(18959),
+ new(20910), new(19089), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(20536), new(20664), new(20625),
+ new(19123), new(14862), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(19833), new(21502), new(17485),
+ new(20267), new(18353), new(23329), new(21478), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(22041), new(23434), new(20001),
+ new(20554), new(20951), new(20145), new(15562), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(23312), new(21607), new(16526),
+ new(18957), new(18034), new(18934), new(24247), new(16921), new(17080),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(26579), new(24910), new(18637),
+ new(19800), new(20388), new(9887), new(15642), new(30198), new(24721),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(26998), new(16737), new(17838),
+ new(18922), new(19515), new(18636), new(17333), new(15776), new(22658),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ new(16384), new(16384), new(16384), new(20177), new(20789), new(20262),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(21416), new(20855), new(23410),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(20238), new(21057), new(19159),
+ new(22337), new(20159), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(20125), new(20559), new(21707),
+ new(22296), new(17333), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(19941), new(20527), new(21470),
+ new(22487), new(19558), new(22354), new(20331), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(22752), new(25006), new(22075),
+ new(21576), new(17740), new(21690), new(19211), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(21442), new(22358), new(18503),
+ new(20291), new(19945), new(21294), new(21178), new(19400), new(10556),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(24648), new(24949), new(20708),
+ new(23905), new(20501), new(9558), new(9423), new(30365), new(19253),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ],
+ [
+ [
+ new(16384), new(16384), new(16384), new(26064), new(22098), new(19613),
+ new(20525), new(17595), new(16618), new(20497), new(18989), new(15513),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ],
+ [
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384), new(16384), new(16384),
+ new(16384), new(16384), new(16384), new(16384)
+ ]
+ ]
+ ]
+ ];
+
+ public static Av1Distribution ChromeForLumaSign => new(1418, 2123, 13340, 18405, 26972, 28343, 32294);
+
+ public static Av1Distribution[] ChromeForLumaAlpha =>
+ [
+ new(7637, 20719, 31401, 32481, 32657, 32688, 32692, 32696, 32700, 32704, 32708, 32712, 32716, 32720, 32724),
+ new(14365, 23603, 28135, 31168, 32167, 32395, 32487, 32573, 32620, 32647, 32668, 32672, 32676, 32680, 32684),
+ new(11532, 22380, 28445, 31360, 32349, 32523, 32584, 32649, 32673, 32677, 32681, 32685, 32689, 32693, 32697),
+ new(26990, 31402, 32282, 32571, 32692, 32696, 32700, 32704, 32708, 32712, 32716, 32720, 32724, 32728, 32732),
+ new(17248, 26058, 28904, 30608, 31305, 31877, 32126, 32321, 32394, 32464, 32516, 32560, 32576, 32593, 32622),
+ new(14738, 21678, 25779, 27901, 29024, 30302, 30980, 31843, 32144, 32413, 32520, 32594, 32622, 32656, 32660)
+ ];
+
+ public static Av1Distribution[][][] GetEndOfBlockFlag(int baseQIndex)
+ {
+ int qContext = GetQContext(baseQIndex);
+ return
+ [
+ EndOfBlockFlagMulti16[qContext],
+ EndOfBlockFlagMulti32[qContext],
+ EndOfBlockFlagMulti64[qContext],
+ EndOfBlockFlagMulti128[qContext],
+ EndOfBlockFlagMulti256[qContext],
+ EndOfBlockFlagMulti512[qContext],
+ EndOfBlockFlagMulti1024[qContext],
+ ];
+ }
+
+ public static Av1Distribution[][][] GetCoefficientsBaseRange(int baseQIndex)
+ => CoefficientsBaseRange[GetQContext(baseQIndex)];
+
+ public static Av1Distribution[][][] GetCoefficientsBase(int baseQIndex)
+ => CoefficientsBase[GetQContext(baseQIndex)];
+
+ public static Av1Distribution[][][] GetBaseEndOfBlock(int baseQIndex)
+ => BaseEndOfBlock[GetQContext(baseQIndex)];
+
+ public static Av1Distribution[][] GetDcSign(int baseQIndex)
+ => DcSign[GetQContext(baseQIndex)];
+
+ public static Av1Distribution[][] GetTransformBlockSkip(int baseQIndex)
+ => TransformBlockSkip[GetQContext(baseQIndex)];
+
+ public static Av1Distribution[][][] GetEndOfBlockExtra(int baseQIndex)
+ => EndOfBlockExtra[GetQContext(baseQIndex)];
+
+ private static int GetQContext(int q)
+ {
+ if (q <= 20)
+ {
+ return 0;
+ }
+
+ if (q <= 60)
+ {
+ return 1;
+ }
+
+ if (q <= 120)
+ {
+ return 2;
+ }
+
+ return 3;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs
new file mode 100644
index 0000000000..1f3cf6916f
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs
@@ -0,0 +1,129 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+///
+/// Class representing the probability distribution used for symbol coding.
+///
+internal class Av1Distribution
+{
+ internal const int ProbabilityTop = 1 << ProbabilityBitCount;
+ internal const int ProbabilityMinimum = 4;
+ internal const int CdfShift = 15 - ProbabilityBitCount;
+ internal const int ProbabilityShift = 6;
+
+ private const int ProbabilityBitCount = 15;
+
+ private readonly uint[] probabilities;
+ private readonly int speed;
+ private int updateCount;
+
+ public Av1Distribution(uint p0)
+ : this([p0, 0], 1)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1)
+ : this([p0, p1, 0], 1)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1, uint p2)
+ : this([p0, p1, p2, 0], 2)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1, uint p2, uint p3)
+ : this([p0, p1, p2, p3, 0], 2)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4)
+ : this([p0, p1, p2, p3, p4, 0], 2)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5)
+ : this([p0, p1, p2, p3, p4, p5, 0], 2)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6)
+ : this([p0, p1, p2, p3, p4, p5, p6, 0], 2)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7)
+ : this([p0, p1, p2, p3, p4, p5, p6, p7, 0], 2)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8)
+ : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, 0], 2)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9)
+ : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, 0], 2)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9, uint p10, uint p11)
+ : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, 0], 2)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9, uint p10, uint p11, uint p12)
+ : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, 0], 2)
+ {
+ }
+
+ public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9, uint p10, uint p11, uint p12, uint p13, uint p14)
+ : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, 0], 2)
+ {
+ }
+
+ private Av1Distribution(uint[] props, int speed)
+ {
+ // this.probabilities = props;
+ this.probabilities = new uint[props.Length];
+ for (int i = 0; i < props.Length - 1; i++)
+ {
+ this.probabilities[i] = ProbabilityTop - props[i];
+ }
+
+ this.NumberOfSymbols = props.Length;
+ this.speed = speed;
+ }
+
+ public int NumberOfSymbols { get; }
+
+ public uint this[int index] => this.probabilities[index];
+
+ public void Update(int value)
+ {
+ int rate15 = this.updateCount > 15 ? 1 : 0;
+ int rate31 = this.updateCount > 31 ? 1 : 0;
+ int rate = 3 + rate15 + rate31 + this.speed; // + get_msb(nsymbs);
+ int tmp = ProbabilityTop;
+
+ // Single loop (faster)
+ for (int i = 0; i < this.NumberOfSymbols - 1; i++)
+ {
+ tmp = (i == value) ? 0 : tmp;
+ uint p = this.probabilities[i];
+ if (tmp < p)
+ {
+ this.probabilities[i] -= (ushort)((p - tmp) >> rate);
+ }
+ else
+ {
+ this.probabilities[i] += (ushort)((tmp - p) >> rate);
+ }
+ }
+
+ int rate32 = this.updateCount < 32 ? 1 : 0;
+ this.updateCount += rate32;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs
new file mode 100644
index 0000000000..eaf33a3d8c
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal enum Av1FilterIntraMode
+{
+ DC,
+ Vertical,
+ Horizontal,
+ Directional157,
+ Paeth,
+ FilterIntraModes,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs
new file mode 100644
index 0000000000..41011d93fd
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs
@@ -0,0 +1,206 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+///
+/// Collection of all information for a single frame.
+///
+internal partial class Av1FrameInfo
+{
+ // Number of Coefficients in a single ModeInfo 4x4 block of pixels (1 length + 4 x 4).
+ public const int CoefficientCountPerModeInfo = 1 + 16;
+
+ private readonly int[] coefficientsY = [];
+ private readonly int[] coefficientsU = [];
+ private readonly int[] coefficientsV = [];
+ private readonly int modeInfoSizePerSuperblock;
+ private readonly int modeInfoCountPerSuperblock;
+ private readonly int superblockColumnCount;
+ private readonly int superblockRowCount;
+ private readonly int subsamplingFactor;
+ private readonly Av1SuperblockInfo[] superblockInfos;
+ private readonly Av1BlockModeInfo[] modeInfos;
+ private readonly Av1FrameModeInfoMap modeInfoMap;
+ private readonly Av1TransformInfo[] transformInfosY;
+ private readonly Av1TransformInfo[] transformInfosUv;
+ private readonly int[] deltaQ;
+ private readonly int cdefStrengthFactorLog2;
+ private readonly int[] cdefStrength;
+ private readonly int deltaLoopFactorLog2 = 2;
+ private readonly int[] deltaLoopFilter;
+
+ public Av1FrameInfo(ObuSequenceHeader sequenceHeader)
+ {
+ // init_main_frame_ctxt
+ int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2;
+ int superblockAlignedWidth = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, superblockSizeLog2);
+ int superblockAlignedHeight = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameHeight, superblockSizeLog2);
+ this.superblockColumnCount = superblockAlignedWidth >> superblockSizeLog2;
+ this.superblockRowCount = superblockAlignedHeight >> superblockSizeLog2;
+ int superblockCount = this.superblockColumnCount * this.superblockRowCount;
+ this.modeInfoSizePerSuperblock = 1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2);
+ this.modeInfoCountPerSuperblock = this.modeInfoSizePerSuperblock * this.modeInfoSizePerSuperblock;
+ int numPlanes = sequenceHeader.ColorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes;
+
+ // Allocate the arrays.
+ this.superblockInfos = new Av1SuperblockInfo[superblockCount];
+ this.modeInfos = new Av1BlockModeInfo[superblockCount * this.modeInfoCountPerSuperblock];
+ this.modeInfoMap = new Av1FrameModeInfoMap(new Size(this.modeInfoCountPerSuperblock * this.superblockColumnCount, this.modeInfoCountPerSuperblock * this.superblockRowCount), superblockSizeLog2);
+ this.transformInfosY = new Av1TransformInfo[superblockCount * this.modeInfoCountPerSuperblock];
+ this.transformInfosUv = new Av1TransformInfo[2 * superblockCount * this.modeInfoCountPerSuperblock];
+
+ // Initialize the arrays.
+ int i = 0;
+ for (int y = 0; y < this.superblockRowCount; y++)
+ {
+ for (int x = 0; x < this.superblockColumnCount; x++)
+ {
+ Point point = new(x, y);
+ this.superblockInfos[i] = new(this, point);
+ i++;
+ }
+ }
+
+ bool subX = sequenceHeader.ColorConfig.SubSamplingX;
+ bool subY = sequenceHeader.ColorConfig.SubSamplingY;
+
+ // Factor: 444 => 0, 422 => 1, 420 => 2.
+ this.subsamplingFactor = (subX && subY) ? 2 : (subX && !subY) ? 1 : (!subX && !subY) ? 0 : -1;
+ Guard.IsFalse(this.subsamplingFactor == -1, nameof(this.subsamplingFactor), "Invalid combination of subsampling.");
+ this.coefficientsY = new int[superblockCount * this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo];
+ this.coefficientsU = new int[(this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo) >> this.subsamplingFactor];
+ this.coefficientsV = new int[(this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo) >> this.subsamplingFactor];
+ this.deltaQ = new int[superblockCount];
+
+ // Superblock size: 128x128 has sizelog2 = 7, 64x64 = 6. Factor should be 128x128 => 4 and 64x64 => 1.
+ this.cdefStrengthFactorLog2 = (superblockSizeLog2 - 6) << 2;
+ this.cdefStrength = new int[superblockCount << this.cdefStrengthFactorLog2];
+ Array.Fill(this.cdefStrength, -1);
+ this.deltaLoopFilter = new int[superblockCount << this.deltaLoopFactorLog2];
+ }
+
+ public int ModeInfoCount => this.modeInfos.Length;
+
+ public Av1SuperblockInfo GetSuperblock(Point index)
+ {
+ Span span = this.superblockInfos;
+ int i = (index.Y * this.superblockColumnCount) + index.X;
+ return span[i];
+ }
+
+ public Av1BlockModeInfo GetModeInfo(Point superblockIndex)
+ {
+ Span span = this.modeInfos;
+ int superblock = (superblockIndex.Y * this.superblockColumnCount) + superblockIndex.X;
+ return span[superblock * this.modeInfoCountPerSuperblock];
+ }
+
+ public Av1BlockModeInfo GetModeInfo(Point superblockIndex, Point modeInfoIndex)
+ {
+ Point location = this.GetModeInfoPosition(superblockIndex, modeInfoIndex);
+ int index = this.modeInfoMap[location];
+ return this.modeInfos[index];
+ }
+
+ public ref Av1TransformInfo GetSuperblockTransform(int plane, Point index)
+ {
+ if (plane == 0)
+ {
+ return ref this.GetSuperblockTransformY(index);
+ }
+
+ return ref this.GetSuperblockTransformUv(index);
+ }
+
+ public ref Av1TransformInfo GetSuperblockTransformY(Point index)
+ {
+ Span span = this.transformInfosY;
+ int offset = ((index.Y * this.superblockColumnCount) + index.X) * this.modeInfoCountPerSuperblock;
+ return ref span[offset];
+ }
+
+ public ref Av1TransformInfo GetSuperblockTransformUv(Point index)
+ {
+ Span span = this.transformInfosUv;
+ int offset = (((index.Y * this.superblockColumnCount) + index.X) * this.modeInfoCountPerSuperblock) << 1;
+ return ref span[offset];
+ }
+
+ public Span GetCoefficients(int plane) =>
+ plane switch
+ {
+ 0 => (Span)this.coefficientsY,
+ 1 => (Span)this.coefficientsY,
+ 2 => (Span)this.coefficientsY,
+ _ => null,
+ };
+
+ public Span GetCoefficientsY(Point index)
+ {
+ Span span = this.coefficientsY;
+ int i = ((index.Y * this.modeInfoCountPerSuperblock) + index.X) * CoefficientCountPerModeInfo;
+ return span.Slice(i, CoefficientCountPerModeInfo);
+ }
+
+ public Span GetCoefficientsU(Point index)
+ {
+ Span span = this.coefficientsU;
+ int i = ((index.Y * this.modeInfoCountPerSuperblock) + index.X) * CoefficientCountPerModeInfo;
+ return span.Slice(i >> this.subsamplingFactor, CoefficientCountPerModeInfo);
+ }
+
+ public Span GetCoefficientsV(Point index)
+ {
+ Span span = this.coefficientsV;
+ int i = ((index.Y * this.modeInfoCountPerSuperblock) + index.X) * CoefficientCountPerModeInfo;
+ return span.Slice(i >> this.subsamplingFactor, CoefficientCountPerModeInfo);
+ }
+
+ public ref int GetDeltaQuantizationIndex(Point index)
+ {
+ Span span = this.deltaQ;
+ int i = (index.Y * this.superblockColumnCount) + index.X;
+ return ref span[i];
+ }
+
+ public Span GetCdefStrength(Point index)
+ {
+ Span span = this.cdefStrength;
+ int i = ((index.Y * this.superblockColumnCount) + index.X) << this.cdefStrengthFactorLog2;
+ return span.Slice(i, 1 << this.cdefStrengthFactorLog2);
+ }
+
+ internal void ClearCdef(Point index)
+ {
+ Span cdefs = this.GetCdefStrength(index);
+ for (int i = 0; i < cdefs.Length; i++)
+ {
+ cdefs[i] = -1;
+ }
+ }
+
+ public Span GetDeltaLoopFilter(Point index)
+ {
+ Span span = this.deltaLoopFilter;
+ int i = ((index.Y * this.superblockColumnCount) + index.X) << this.deltaLoopFactorLog2;
+ return span.Slice(i, 1 << this.deltaLoopFactorLog2);
+ }
+
+ public void ClearDeltaLoopFilter() => Array.Fill(this.deltaLoopFilter, 0);
+
+ public void UpdateModeInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo)
+ {
+ this.modeInfos[this.modeInfoMap.NextIndex] = modeInfo;
+ this.modeInfoMap.Update(this.GetModeInfoPosition(superblockInfo.Position, modeInfo.PositionInSuperblock), modeInfo.BlockSize);
+ }
+
+ private Point GetModeInfoPosition(Point superblockPosition, Point positionInSuperblock)
+ {
+ int x = (superblockPosition.X * this.modeInfoCountPerSuperblock) + positionInSuperblock.X;
+ int y = (superblockPosition.Y * this.modeInfoCountPerSuperblock) + positionInSuperblock.Y;
+ return new Point(x, y);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs
new file mode 100644
index 0000000000..6867b3966d
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal partial class Av1FrameInfo
+{
+ ///
+ /// Mapping of instances, from position to index into the .
+ ///
+ ///
+ /// For a visual representation of how this map looks in practice, see
+ ///
+ public class Av1FrameModeInfoMap
+ {
+ private readonly int[] offsets;
+ private Size alignedModeInfoCount;
+
+ public Av1FrameModeInfoMap(Size modeInfoCount, int superblockSizeLog2)
+ {
+ this.alignedModeInfoCount = new Size(
+ modeInfoCount.Width * (1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2)),
+ modeInfoCount.Height * (1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2)));
+ this.NextIndex = 0;
+ this.offsets = new int[this.alignedModeInfoCount.Width * this.alignedModeInfoCount.Height];
+ }
+
+ ///
+ /// Gets the next index to use.
+ ///
+ public int NextIndex { get; private set; }
+
+ ///
+ /// Gets the mapped index for the given location.
+ ///
+ public int this[Point location]
+ {
+ get
+ {
+ int index = (location.Y * this.alignedModeInfoCount.Width) + location.X;
+ return this.offsets[index];
+ }
+ }
+
+ public void Update(Point modeInfoLocation, Av1BlockSize blockSize)
+ {
+ // Equivalent in SVT-Av1: EbDecNbr.c svt_aom_update_block_nbrs
+ int bw4 = blockSize.Get4x4WideCount();
+ int bh4 = blockSize.Get4x4HighCount();
+ DebugGuard.MustBeGreaterThanOrEqualTo(modeInfoLocation.Y, 0, nameof(modeInfoLocation));
+ DebugGuard.MustBeLessThanOrEqualTo(modeInfoLocation.Y + bh4, this.alignedModeInfoCount.Height, nameof(modeInfoLocation));
+ DebugGuard.MustBeGreaterThanOrEqualTo(modeInfoLocation.X, 0, nameof(modeInfoLocation));
+ DebugGuard.MustBeLessThanOrEqualTo(modeInfoLocation.X + bw4, this.alignedModeInfoCount.Width, nameof(modeInfoLocation));
+ /* Update 4x4 nbr offset map */
+ for (int i = modeInfoLocation.Y; i < modeInfoLocation.Y + bh4; i++)
+ {
+ Array.Fill(this.offsets, this.NextIndex, (i * this.alignedModeInfoCount.Width) + modeInfoLocation.X, bw4);
+ }
+
+ this.NextIndex++;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs
new file mode 100644
index 0000000000..649a069b0b
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1IntraFilterModeInfo
+{
+ public bool UseFilterIntra { get; set; }
+
+ public Av1FilterIntraMode Mode { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs
new file mode 100644
index 0000000000..c5505899ad
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs
@@ -0,0 +1,356 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal static class Av1NzMap
+{
+ private static readonly int[] ClipMax3 = [
+ 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
+ ];
+
+ // SIG_COEF_CONTEXTS_2D = 26
+ private const int NzMapContext0 = 26;
+ private const int NzMapContext5 = NzMapContext0 + 5;
+ private const int NzMapContext10 = NzMapContext0 + 10;
+
+ private static readonly int[] NzMapContextOffset1d = [
+ NzMapContext0, NzMapContext5, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
+ NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
+ NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
+ NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
+ NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
+ ];
+
+ // The ctx offset table when TX is TX_CLASS_2D.
+ // TX col and row indices are clamped to 4
+ private static readonly int[] NzMapContextOffset4x4 = [0, 1, 6, 6, 1, 6, 6, 21, 6, 6, 21, 21, 6, 21, 21, 21];
+
+ private static readonly int[] NzMapContextOffset8x8 = [
+ 0, 1, 6, 6, 21, 21, 21, 21, 1, 6, 6, 21, 21, 21, 21, 21, 6, 6, 21, 21, 21, 21,
+ 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset16x16 = [
+ 0, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset32x32 = [
+ 0, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset8x4 = [
+ 0, 16, 6, 6, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21,
+ 16, 16, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset16x8 = [
+ 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset16x32 = [
+ 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset32x16 = [
+ 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset32x64 = [
+ 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset64x32 = [
+ 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16,
+ 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset4x16 = [
+ 0, 11, 11, 11, 11, 11, 11, 11, 6, 6, 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset16x4 = [
+ 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset8x32 = [
+ 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 6, 6, 21, 21, 21, 21, 21, 21, 6, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[] NzMapContextOffset32x8 = [
+ 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ ];
+
+ private static readonly int[][] NzMapContextOffset = [
+ NzMapContextOffset4x4, // TX_4x4
+ NzMapContextOffset8x8, // TX_8x8
+ NzMapContextOffset16x16, // TX_16x16
+ NzMapContextOffset32x32, // TX_32x32
+ NzMapContextOffset32x32, // TX_32x32
+ NzMapContextOffset4x16, // TX_4x8
+ NzMapContextOffset8x4, // TX_8x4
+ NzMapContextOffset8x32, // TX_8x16
+ NzMapContextOffset16x8, // TX_16x8
+ NzMapContextOffset16x32, // TX_16x32
+ NzMapContextOffset32x16, // TX_32x16
+ NzMapContextOffset32x64, // TX_32x64
+ NzMapContextOffset64x32, // TX_64x32
+ NzMapContextOffset4x16, // TX_4x16
+ NzMapContextOffset16x4, // TX_16x4
+ NzMapContextOffset8x32, // TX_8x32
+ NzMapContextOffset32x8, // TX_32x8
+ NzMapContextOffset16x32, // TX_16x64
+ NzMapContextOffset64x32, // TX_64x16
+ ];
+
+ public static int GetNzMagnitude(Span levels, int bwl, Av1TransformClass transformClass)
+ {
+ int mag;
+
+ // Note: AOMMIN(level, 3) is useless for decoder since level < 3.
+ mag = ClipMax3[levels[1]]; // { 0, 1 }
+ mag += ClipMax3[levels[(1 << bwl) + Av1Constants.TransformPadHorizontal]]; // { 1, 0 }
+
+ switch (transformClass)
+ {
+ case Av1TransformClass.Class2D:
+ mag += ClipMax3[levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1]]; // { 1, 1 }
+ mag += ClipMax3[levels[2]]; // { 0, 2 }
+ mag += ClipMax3[levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)]]; // { 2, 0 }
+ break;
+
+ case Av1TransformClass.ClassVertical:
+ mag += ClipMax3[levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)]]; // { 2, 0 }
+ mag += ClipMax3[levels[(3 << bwl) + (3 << Av1Constants.TransformPadHorizontalLog2)]]; // { 3, 0 }
+ mag += ClipMax3[levels[(4 << bwl) + (4 << Av1Constants.TransformPadHorizontalLog2)]]; // { 4, 0 }
+ break;
+ case Av1TransformClass.ClassHorizontal:
+ mag += ClipMax3[levels[2]]; // { 0, 2 }
+ mag += ClipMax3[levels[3]]; // { 0, 3 }
+ mag += ClipMax3[levels[4]]; // { 0, 4 }
+ break;
+ }
+
+ return mag;
+ }
+
+ public static int GetNzMapContextFromStats(int stats, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass)
+ {
+ // tx_class == 0(TX_CLASS_2D)
+ if (((int)transformClass | pos) == 0)
+ {
+ return 0;
+ }
+
+ int ctx = (stats + 1) >> 1;
+ ctx = Math.Min(ctx, 4);
+ switch (transformClass)
+ {
+ case Av1TransformClass.Class2D:
+ // This is the algorithm to generate eb_av1_nz_map_ctx_offset[][]
+ // const int width = tx_size_wide[tx_size];
+ // const int height = tx_size_high[tx_size];
+ // if (width < height) {
+ // if (row < 2) return 11 + ctx;
+ // } else if (width > height) {
+ // if (col < 2) return 16 + ctx;
+ // }
+ // if (row + col < 2) return ctx + 1;
+ // if (row + col < 4) return 5 + ctx + 1;
+ // return 21 + ctx;
+ return ctx + NzMapContextOffset[(int)transformSize][pos];
+ case Av1TransformClass.ClassHorizontal:
+ int row = pos >> bwl;
+ int col = pos - (row << bwl);
+ return ctx + NzMapContextOffset1d[col];
+ case Av1TransformClass.ClassVertical:
+ int row2 = pos >> bwl;
+ return ctx + NzMapContextOffset1d[row2];
+ default:
+ break;
+ }
+
+ return 0;
+ }
+
+ public static int GetNzMapContext(Av1TransformSize transformSize, int pos) => NzMapContextOffset[(int)transformSize][pos];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs
new file mode 100644
index 0000000000..9cae5c5254
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs
@@ -0,0 +1,93 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1ParseAboveNeighbor4x4Context
+{
+ /* Buffer holding the sign of the DC coefficients and the
+ cumulative sum of the coefficient levels of the above 4x4
+ blocks corresponding to the current super block row. */
+ private readonly int[][] aboveContext = new int[Av1Constants.MaxPlanes][];
+
+ /* Buffer holding the seg_id_predicted of the previous 4x4 block row. */
+ private readonly int[] aboveSegmentIdPredictionContext;
+
+ /* Value of base colors for Y, U, and V */
+ private readonly int[][] abovePaletteColors = new int[Av1Constants.MaxPlanes][];
+
+ private readonly int[] aboveCompGroupIndex;
+
+ public Av1ParseAboveNeighbor4x4Context(int planesCount, int modeInfoColumnCount)
+ {
+ int wide64x64Count = Av1BlockSize.Block64x64.Get4x4WideCount();
+ this.AboveTransformWidth = new int[modeInfoColumnCount];
+ this.AbovePartitionWidth = new int[modeInfoColumnCount];
+ for (int i = 0; i < planesCount; i++)
+ {
+ this.aboveContext[i] = new int[modeInfoColumnCount];
+ this.abovePaletteColors[i] = new int[wide64x64Count * Av1Constants.PaletteMaxSize];
+ }
+
+ this.aboveSegmentIdPredictionContext = new int[modeInfoColumnCount];
+ this.aboveCompGroupIndex = new int[modeInfoColumnCount];
+ }
+
+ ///
+ /// Gets a buffer holding the partition context of the previous 4x4 block row.
+ ///
+ public int[] AbovePartitionWidth { get; }
+
+ ///
+ /// Gets a buffer holding the transform sizes of the previous 4x4 block row.
+ ///
+ public int[] AboveTransformWidth { get; }
+
+ public int[] GetContext(int plane) => this.aboveContext[plane];
+
+ public void Clear(ObuSequenceHeader sequenceHeader, int modeInfoColumnStart, int modeInfoColumnEnd)
+ {
+ int planeCount = sequenceHeader.ColorConfig.PlaneCount;
+ int width = modeInfoColumnEnd - modeInfoColumnStart;
+ Array.Fill(this.AboveTransformWidth, Av1TransformSize.Size64x64.GetWidth(), 0, width);
+ Array.Fill(this.AbovePartitionWidth, 0, 0, width);
+ for (int i = 0; i < planeCount; i++)
+ {
+ Array.Fill(this.aboveContext[i], 0, 0, width);
+ Array.Fill(this.abovePaletteColors[i], 0, 0, width);
+ }
+
+ Array.Fill(this.aboveSegmentIdPredictionContext, 0, 0, width);
+ Array.Fill(this.aboveCompGroupIndex, 0, 0, width);
+ }
+
+ public void UpdatePartition(Point modeInfoLocation, Av1TileInfo tileLoc, Av1BlockSize subSize, Av1BlockSize blockSize)
+ {
+ int startIndex = modeInfoLocation.X - tileLoc.ModeInfoColumnStart;
+ int bw = blockSize.Get4x4WideCount();
+ int value = Av1PartitionContext.GetAboveContext(subSize);
+
+ DebugGuard.MustBeLessThanOrEqualTo(startIndex, this.AboveTransformWidth.Length - bw, nameof(startIndex));
+ Array.Fill(this.AbovePartitionWidth, value, startIndex, bw);
+ }
+
+ public void UpdateTransformation(Point modeInfoLocation, Av1TileInfo tileInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip)
+ {
+ int startIndex = modeInfoLocation.X - tileInfo.ModeInfoColumnStart;
+ int transformWidth = transformSize.GetWidth();
+ int n4w = blockSize.Get4x4WideCount();
+ if (skip)
+ {
+ transformWidth = n4w << Av1Constants.ModeInfoSizeLog2;
+ }
+
+ DebugGuard.MustBeLessThanOrEqualTo(startIndex, this.AboveTransformWidth.Length - n4w, nameof(startIndex));
+ Array.Fill(this.AboveTransformWidth, transformWidth, startIndex, n4w);
+ }
+
+ internal void ClearContext(int plane, int offset, int length)
+ => Array.Fill(this.aboveContext[plane], 0, offset, length);
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs
new file mode 100644
index 0000000000..e61c7b563e
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs
@@ -0,0 +1,94 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1ParseLeftNeighbor4x4Context
+{
+ /* Buffer holding the sign of the DC coefficients and the
+ cumulative sum of the coefficient levels of the left 4x4
+ blocks corresponding to the current super block row. */
+ private readonly int[][] leftContext = new int[Av1Constants.MaxPlanes][];
+
+ /* Buffer holding the seg_id_predicted of the previous 4x4 block row. */
+ private readonly int[] leftSegmentIdPredictionContext;
+
+ /* Value of base colors for Y, U, and V */
+ private readonly int[][] leftPaletteColors = new int[Av1Constants.MaxPlanes][];
+
+ private readonly int[] leftCompGroupIndex;
+
+ public Av1ParseLeftNeighbor4x4Context(int planesCount, int superblockModeInfoSize)
+ {
+ this.LeftTransformHeight = new int[superblockModeInfoSize];
+ this.LeftPartitionHeight = new int[superblockModeInfoSize];
+ for (int i = 0; i < planesCount; i++)
+ {
+ this.leftContext[i] = new int[superblockModeInfoSize];
+ this.leftPaletteColors[i] = new int[superblockModeInfoSize * Av1Constants.PaletteMaxSize];
+ }
+
+ this.leftSegmentIdPredictionContext = new int[superblockModeInfoSize];
+ this.leftCompGroupIndex = new int[superblockModeInfoSize];
+ }
+
+ ///
+ /// Gets a buffer holding the partition context of the left 4x4 blocks corresponding
+ /// to the current super block row.
+ ///
+ public int[] LeftPartitionHeight { get; }
+
+ ///
+ /// Gets a buffer holding the transform sizes of the left 4x4 blocks corresponding
+ /// to the current super block row.
+ ///
+ public int[] LeftTransformHeight { get; }
+
+ public void Clear(ObuSequenceHeader sequenceHeader)
+ {
+ int blockCount = sequenceHeader.SuperblockModeInfoSize;
+ int planeCount = sequenceHeader.ColorConfig.PlaneCount;
+ int neighbor4x4Count = sequenceHeader.SuperblockModeInfoSize;
+ Array.Fill(this.LeftTransformHeight, Av1TransformSize.Size64x64.GetHeight(), 0, blockCount);
+ Array.Fill(this.LeftPartitionHeight, 0, 0, blockCount);
+ for (int i = 0; i < planeCount; i++)
+ {
+ Array.Fill(this.leftContext[i], 0, 0, blockCount);
+ Array.Fill(this.leftPaletteColors[i], 0, 0, blockCount);
+ }
+
+ Array.Fill(this.leftSegmentIdPredictionContext, 0, 0, blockCount);
+ Array.Fill(this.leftCompGroupIndex, 0, 0, blockCount);
+ }
+
+ public void UpdatePartition(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize)
+ {
+ int startIndex = (modeInfoLocation.Y - superblockInfo.Position.Y) & Av1PartitionContext.Mask;
+ int bh = blockSize.Get4x4HighCount();
+ int value = Av1PartitionContext.GetLeftContext(subSize);
+ DebugGuard.MustBeLessThanOrEqualTo(startIndex, this.LeftTransformHeight.Length - bh, nameof(startIndex));
+ Array.Fill(this.LeftPartitionHeight, value, startIndex, bh);
+ }
+
+ public void UpdateTransformation(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip)
+ {
+ int startIndex = modeInfoLocation.Y - superblockInfo.Position.Y;
+ int transformHeight = transformSize.GetHeight();
+ int n4h = blockSize.Get4x4HighCount();
+ if (skip)
+ {
+ transformHeight = n4h << Av1Constants.ModeInfoSizeLog2;
+ }
+
+ DebugGuard.MustBeLessThanOrEqualTo(startIndex, this.LeftTransformHeight.Length - n4h, nameof(startIndex));
+ Array.Fill(this.LeftTransformHeight, transformHeight, startIndex, n4h);
+ }
+
+ internal void ClearContext(int plane, int offset, int length)
+ => Array.Fill(this.leftContext[plane], 0, offset, length);
+
+ internal int[] GetContext(int plane) => this.leftContext[plane];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs
new file mode 100644
index 0000000000..2289d28d78
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+// Generates 5 bit field in which each bit set to 1 represents
+// a BlockSize partition 11111 means we split 128x128, 64x64, 32x32, 16x16
+// and 8x8. 10000 means we just split the 128x128 to 64x64
+internal class Av1PartitionContext
+{
+ private static readonly int[] AboveLookup =
+ [31, 31, 30, 30, 30, 28, 28, 28, 24, 24, 24, 16, 16, 16, 0, 0, 31, 28, 30, 24, 28, 16];
+
+ private static readonly int[] LeftLookup =
+ [31, 30, 31, 30, 28, 30, 28, 24, 28, 24, 16, 24, 16, 0, 16, 0, 28, 31, 24, 30, 16, 28];
+
+ // Mask to extract ModeInfo offset within max ModeInfoBlock
+ public const int Mask = (1 << (7 - 2)) - 1;
+
+ public static int GetAboveContext(Av1BlockSize blockSize) => AboveLookup[(int)blockSize];
+
+ public static int GetLeftContext(Av1BlockSize blockSize) => LeftLookup[(int)blockSize];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs
new file mode 100644
index 0000000000..30d506f0cc
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs
@@ -0,0 +1,148 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1PartitionInfo
+{
+ public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo, bool isChroma, Av1PartitionType partitionType)
+ {
+ this.ModeInfo = modeInfo;
+ this.SuperblockInfo = superblockInfo;
+ this.IsChroma = isChroma;
+ this.Type = partitionType;
+ this.CdefStrength = [];
+ this.ReferenceFrame = [-1, -1];
+ this.WidthInPixels = new int[3];
+ this.HeightInPixels = new int[3];
+ }
+
+ public Av1BlockModeInfo ModeInfo { get; }
+
+ ///
+ /// Gets the this partition resides inside.
+ ///
+ public Av1SuperblockInfo SuperblockInfo { get; }
+
+ public bool IsChroma { get; }
+
+ public Av1PartitionType Type { get; }
+
+ ///
+ /// Gets or sets a value indicating whether the information from the block above can be used on the luma plane.
+ ///
+ public bool AvailableAbove { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the information from the block left can be used on the luma plane.
+ ///
+ public bool AvailableLeft { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the information from the block above can be used on the chroma plane.
+ ///
+ public bool AvailableAboveForChroma { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the information from the block left can be used on the chroma plane.
+ ///
+ public bool AvailableLeftForChroma { get; set; }
+
+ ///
+ /// Gets or sets the horizontal location of the block in units of 4x4 luma samples.
+ ///
+ public int ColumnIndex { get; set; }
+
+ ///
+ /// Gets or sets the vertical location of the block in units of 4x4 luma samples.
+ ///
+ public int RowIndex { get; set; }
+
+ public Av1BlockModeInfo? AboveModeInfo { get; set; }
+
+ public Av1BlockModeInfo? LeftModeInfo { get; set; }
+
+ public Av1BlockModeInfo? AboveModeInfoForChroma { get; set; }
+
+ public Av1BlockModeInfo? LeftModeInfoForChroma { get; set; }
+
+ public int[][] CdefStrength { get; set; }
+
+ public int[] ReferenceFrame { get; set; }
+
+ public int ModeBlockToLeftEdge { get; private set; }
+
+ public int ModeBlockToRightEdge { get; private set; }
+
+ public int ModeBlockToTopEdge { get; private set; }
+
+ public int ModeBlockToBottomEdge { get; private set; }
+
+ public int[] WidthInPixels { get; private set; }
+
+ public int[] HeightInPixels { get; private set; }
+
+ public Av1ChromaFromLumaContext? ChromaFromLumaContext { get; internal set; }
+
+ public void ComputeBoundaryOffsets(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1TileInfo tileInfo)
+ {
+ Av1BlockSize blockSize = this.ModeInfo.BlockSize;
+ int bw4 = blockSize.Get4x4WideCount();
+ int bh4 = blockSize.Get4x4HighCount();
+ int subX = sequenceHeader.ColorConfig.SubSamplingX ? 1 : 0;
+ int subY = sequenceHeader.ColorConfig.SubSamplingY ? 1 : 0;
+ this.AvailableAbove = this.RowIndex > tileInfo.ModeInfoRowStart;
+ this.AvailableLeft = this.ColumnIndex > tileInfo.ModeInfoColumnStart;
+ this.AvailableAboveForChroma = this.AvailableAbove;
+ this.AvailableLeftForChroma = this.AvailableLeft;
+
+ int shift = Av1Constants.ModeInfoSizeLog2 + 3;
+ this.ModeBlockToLeftEdge = -this.ColumnIndex << shift;
+ this.ModeBlockToRightEdge = (frameHeader.ModeInfoColumnCount - bw4 - this.ColumnIndex) << shift;
+ this.ModeBlockToTopEdge = -this.RowIndex << shift;
+ this.ModeBlockToBottomEdge = (frameHeader.ModeInfoRowCount - bh4 - this.RowIndex) << shift;
+
+ // Block Size width & height in pixels.
+ // For Luma bock
+ const int modeInfoSize = 1 << Av1Constants.ModeInfoSizeLog2;
+ this.WidthInPixels[0] = bw4 * modeInfoSize;
+ this.HeightInPixels[0] = bh4 * modeInfoSize;
+
+ // For U plane chroma bock
+ this.WidthInPixels[1] = Math.Max(1, bw4 >> subX) * modeInfoSize;
+ this.HeightInPixels[1] = Math.Max(1, bh4 >> subY) * modeInfoSize;
+
+ // For V plane chroma bock
+ this.WidthInPixels[2] = Math.Max(1, bw4 >> subX) * modeInfoSize;
+ this.HeightInPixels[2] = Math.Max(1, bh4 >> subY) * modeInfoSize;
+
+ this.ChromaFromLumaContext = new Av1ChromaFromLumaContext(configuration, sequenceHeader.ColorConfig);
+ }
+
+ public int GetMaxBlockWide(Av1BlockSize blockSize, bool subX)
+ {
+ int maxBlockWide = blockSize.GetWidth();
+ if (this.ModeBlockToRightEdge < 0)
+ {
+ int shift = subX ? 4 : 3;
+ maxBlockWide += this.ModeBlockToRightEdge >> shift;
+ }
+
+ return maxBlockWide >> 2;
+ }
+
+ public int GetMaxBlockHigh(Av1BlockSize blockSize, bool subY)
+ {
+ int maxBlockHigh = blockSize.GetHeight();
+ if (this.ModeBlockToBottomEdge < 0)
+ {
+ int shift = subY ? 4 : 3;
+ maxBlockHigh += this.ModeBlockToBottomEdge >> shift;
+ }
+
+ return maxBlockHigh >> 2;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs
new file mode 100644
index 0000000000..3c790f5092
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs
@@ -0,0 +1,10 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal enum Av1PlaneType : int
+{
+ Y,
+ Uv
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs
new file mode 100644
index 0000000000..05ac285fdb
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1SuperblockInfo
+{
+ private readonly Av1FrameInfo frameInfo;
+
+ public Av1SuperblockInfo(Av1FrameInfo frameInfo, Point position)
+ {
+ this.Position = position;
+ this.frameInfo = frameInfo;
+ }
+
+ ///
+ /// Gets the position of this superblock inside the tile, counted in superblocks.
+ ///
+ public Point Position { get; }
+
+ public ref int SuperblockDeltaQ => ref this.frameInfo.GetDeltaQuantizationIndex(this.Position);
+
+ public Av1BlockModeInfo SuperblockModeInfo => this.GetModeInfo(new Point(0, 0));
+
+ public Span CoefficientsY => this.frameInfo.GetCoefficientsY(this.Position);
+
+ public Span CoefficientsU => this.frameInfo.GetCoefficientsU(this.Position);
+
+ public Span CoefficientsV => this.frameInfo.GetCoefficientsV(this.Position);
+
+ public Span CdefStrength => this.frameInfo.GetCdefStrength(this.Position);
+
+ public Span SuperblockDeltaLoopFilter => this.frameInfo.GetDeltaLoopFilter(this.Position);
+
+ public int TransformInfoIndexY { get; internal set; }
+
+ public int TransformInfoIndexUv { get; internal set; }
+
+ public int BlockCount { get; internal set; }
+
+ public ref Av1TransformInfo GetTransformInfoY() => ref this.frameInfo.GetSuperblockTransformY(this.Position);
+
+ public ref Av1TransformInfo GetTransformInfoUv() => ref this.frameInfo.GetSuperblockTransformUv(this.Position);
+
+ public Av1BlockModeInfo GetModeInfo(Point index) => this.frameInfo.GetModeInfo(this.Position, index);
+
+ public Span GetCoefficients(Av1Plane plane) => plane switch
+ {
+ Av1Plane.Y => this.CoefficientsY,
+ Av1Plane.U => this.CoefficientsU,
+ Av1Plane.V => this.CoefficientsV,
+ _ => []
+ };
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs
new file mode 100644
index 0000000000..62a3894d45
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs
@@ -0,0 +1,264 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal ref struct Av1SymbolDecoder
+{
+ private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0];
+ private static readonly int[] AlphaVContexts = [-1, 0, 3, -1, 1, 4, -1, 2, 5];
+
+ private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
+ private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes;
+ private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode;
+ private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode;
+ private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip;
+ private readonly Av1Distribution deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute;
+ private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute;
+ private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId;
+ private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta;
+ private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode;
+ private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra;
+ private readonly Av1Distribution[][] transformSize = Av1DefaultDistributions.TransformSize;
+ private readonly Av1Distribution[][][] endOfBlockFlag;
+ private readonly Av1Distribution[][][] coefficientsBase;
+ private readonly Av1Distribution[][][] baseEndOfBlock;
+ private readonly Av1Distribution[][] dcSign;
+ private readonly Av1Distribution[][][] coefficientsBaseRange;
+ private readonly Av1Distribution[][] transformBlockSkip;
+ private readonly Av1Distribution[][][] endOfBlockExtra;
+ private readonly Av1Distribution chromeForLumaSign = Av1DefaultDistributions.ChromeForLumaSign;
+ private readonly Av1Distribution[] chromeForLumaAlpha = Av1DefaultDistributions.ChromeForLumaAlpha;
+ private Av1SymbolReader reader;
+
+ public Av1SymbolDecoder(Span tileData, int qIndex)
+ {
+ this.reader = new Av1SymbolReader(tileData);
+ this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex);
+ this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex);
+ this.baseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex);
+ this.dcSign = Av1DefaultDistributions.GetDcSign(qIndex);
+ this.coefficientsBaseRange = Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex);
+ this.transformBlockSkip = Av1DefaultDistributions.GetTransformBlockSkip(qIndex);
+ this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex);
+ }
+
+ public int ReadLiteral(int bitCount)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadLiteral(bitCount);
+ }
+
+ public bool ReadUseIntraBlockCopy()
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.tileIntraBlockCopy) > 0;
+ }
+
+ public Av1PartitionType ReadPartitionType(int context)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return (Av1PartitionType)r.ReadSymbol(this.tilePartitionTypes[context]);
+ }
+
+ public bool ReadSplitOrHorizontal(Av1BlockSize blockSize, int context)
+ {
+ Av1Distribution input = this.tilePartitionTypes[context];
+ uint p = Av1Distribution.ProbabilityTop;
+ p -= GetElementProbability(input, Av1PartitionType.Horizontal);
+ p -= GetElementProbability(input, Av1PartitionType.Split);
+ p -= GetElementProbability(input, Av1PartitionType.HorizontalA);
+ p -= GetElementProbability(input, Av1PartitionType.HorizontalB);
+ p -= GetElementProbability(input, Av1PartitionType.VerticalA);
+ if (blockSize != Av1BlockSize.Block128x128)
+ {
+ p -= GetElementProbability(input, Av1PartitionType.Horizontal4);
+ }
+
+ Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p);
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(distribution) > 0;
+ }
+
+ public bool ReadSplitOrVertical(Av1BlockSize blockSize, int context)
+ {
+ Av1Distribution input = this.tilePartitionTypes[context];
+ uint p = Av1Distribution.ProbabilityTop;
+ p -= GetElementProbability(input, Av1PartitionType.Vertical);
+ p -= GetElementProbability(input, Av1PartitionType.Split);
+ p -= GetElementProbability(input, Av1PartitionType.HorizontalA);
+ p -= GetElementProbability(input, Av1PartitionType.VerticalA);
+ p -= GetElementProbability(input, Av1PartitionType.VerticalB);
+ if (blockSize != Av1BlockSize.Block128x128)
+ {
+ p -= GetElementProbability(input, Av1PartitionType.Vertical4);
+ }
+
+ Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p);
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(distribution) > 0;
+ }
+
+ public Av1PredictionMode ReadYMode(Av1BlockModeInfo? aboveModeInfo, Av1BlockModeInfo? leftModeInfo)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ Av1PredictionMode aboveMode = Av1PredictionMode.DC;
+ if (aboveModeInfo != null)
+ {
+ aboveMode = aboveModeInfo.YMode;
+ }
+
+ Av1PredictionMode leftMode = Av1PredictionMode.DC;
+ if (leftModeInfo != null)
+ {
+ leftMode = leftModeInfo.YMode;
+ }
+
+ int aboveContext = IntraModeContext[(int)aboveMode];
+ int leftContext = IntraModeContext[(int)leftMode];
+ return (Av1PredictionMode)r.ReadSymbol(this.keyFrameYMode[aboveContext][leftContext]);
+ }
+
+ public Av1PredictionMode ReadIntraModeUv(Av1PredictionMode mode, bool chromaFromLumaAllowed)
+ {
+ int chromaForLumaIndex = chromaFromLumaAllowed ? 1 : 0;
+ ref Av1SymbolReader r = ref this.reader;
+ return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)mode]);
+ }
+
+ public bool ReadSkip(int ctx)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.skip[ctx]) > 0;
+ }
+
+ public int ReadDeltaLoopFilterAbsolute()
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.deltaLoopFilterAbsolute);
+ }
+
+ public int ReadDeltaQuantizerAbsolute()
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.deltaQuantizerAbsolute);
+ }
+
+ public int ReadSegmentId(int ctx)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.segmentId[ctx]);
+ }
+
+ public int ReadAngleDelta(Av1PredictionMode mode)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.angleDelta[((int)mode) - 1]);
+ }
+
+ public bool ReadUseFilterUltra(Av1BlockSize blockSize)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.filterIntra[(int)blockSize]) > 0;
+ }
+
+ public Av1FilterIntraMode ReadFilterUltraMode()
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return (Av1FilterIntraMode)r.ReadSymbol(this.filterIntraMode);
+ }
+
+ public Av1TransformSize ReadTransformSize(Av1BlockSize blockSize, int context)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ Av1TransformSize maxTransformSize = blockSize.GetMaximumTransformSize();
+ int depth = 0;
+ while (maxTransformSize != Av1TransformSize.Size4x4)
+ {
+ depth++;
+ maxTransformSize = maxTransformSize.GetSubSize();
+ DebugGuard.MustBeLessThan(depth, 10, nameof(depth));
+ }
+
+ DebugGuard.MustBeLessThanOrEqualTo(depth, Av1Constants.MaxTransformCategories, nameof(depth));
+ int category = depth - 1;
+ int value = r.ReadSymbol(this.transformSize[category][context]);
+ Av1TransformSize transformSize = blockSize.GetMaximumTransformSize();
+ for (int d = 0; d < value; ++d)
+ {
+ transformSize = transformSize.GetSubSize();
+ }
+
+ return transformSize;
+ }
+
+ public int ReadEndOfBlockFlag(Av1PlaneType planeType, Av1TransformClass transformClass, Av1TransformSize transformSize)
+ {
+ int endOfBlockContext = transformClass == Av1TransformClass.Class2D ? 0 : 1;
+ int endOfBlockMultiSize = transformSize.GetLog2Minus4();
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.endOfBlockFlag[endOfBlockMultiSize][(int)planeType][endOfBlockContext]) + 1;
+ }
+
+ public bool ReadTransformBlockSkip(Av1TransformSize transformSizeContext, int skipContext)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.transformBlockSkip[(int)transformSizeContext][skipContext]) > 0;
+ }
+
+ public bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.endOfBlockExtra[(int)transformSizeContext][(int)planeType][endOfBlockContext]) > 0;
+ }
+
+ public int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.coefficientsBaseRange[(int)transformSizeContext][(int)planeType][baseRangeContext]);
+ }
+
+ public int ReadDcSign(Av1PlaneType planeType, int dcSignContext)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.dcSign[(int)planeType][dcSignContext]);
+ }
+
+ public int ReadBaseEndOfBlock(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.baseEndOfBlock[(int)transformSizeContext][(int)planeType][coefficientContext]);
+ }
+
+ public int ReadCoefficientsBase(int coefficientContext, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]);
+ }
+
+ public int ReadChromFromLumaSign()
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ return r.ReadSymbol(this.chromeForLumaSign);
+ }
+
+ public int ReadChromaFromLumaAlphaU(int jointSignPlus1)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ int context = jointSignPlus1 - 3;
+ return r.ReadSymbol(this.chromeForLumaAlpha[context]);
+ }
+
+ public int ReadChromaFromLumaAlphaV(int jointSignPlus1)
+ {
+ ref Av1SymbolReader r = ref this.reader;
+ int context = AlphaVContexts[jointSignPlus1];
+ return r.ReadSymbol(this.chromeForLumaAlpha[context]);
+ }
+
+ private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element)
+ => probability[(int)element - 1] - probability[(int)element];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs
new file mode 100644
index 0000000000..3da3237c26
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1SymbolEncoder : IDisposable
+{
+ private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
+ private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes;
+
+ private Av1SymbolWriter? writer;
+
+ public Av1SymbolEncoder(Configuration configuration, int initialSize)
+ => this.writer = new(configuration, initialSize);
+
+ public void WriteUseIntraBlockCopy(bool value)
+ => this.writer!.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy);
+
+ public void WritePartitionType(Av1PartitionType value, int context)
+ => this.writer!.WriteSymbol((int)value, this.tilePartitionTypes[context]);
+
+ public IMemoryOwner Exit() => this.writer!.Exit();
+
+ public void Dispose()
+ {
+ this.writer?.Dispose();
+ this.writer = null;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs
new file mode 100644
index 0000000000..112151b152
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs
@@ -0,0 +1,207 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal ref struct Av1SymbolReader
+{
+ private const int DecoderWindowsSize = 32;
+ private const int LotsOfBits = 0x4000;
+
+ private readonly Span buffer;
+ private int position;
+
+ /*
+ * The difference between the high end of the current range, (low + rng), and
+ * the coded value, minus 1.
+ * This stores up to OD_EC_WINDOW_SIZE bits of that difference, but the
+ * decoder only uses the top 16 bits of the window to decode the next symbol.
+ * As we shift up during renormalization, if we don't have enough bits left in
+ * the window to fill the top 16, we'll read in more bits of the coded
+ * value.
+ */
+ private uint difference;
+
+ // The number of values in the current range.
+ private uint range;
+
+ // The number of bits in the current value.
+ private int count;
+
+ public Av1SymbolReader(Span span)
+ {
+ this.buffer = span;
+ this.position = 0;
+ this.difference = (1U << (DecoderWindowsSize - 1)) - 1;
+ this.range = 0x8000;
+ this.count = -15;
+ this.Refill();
+ }
+
+ public int ReadSymbol(Av1Distribution distribution)
+ {
+ int value = this.DecodeIntegerQ15(distribution);
+
+ // UpdateCdf(probabilities, value, numberOfSymbols);
+ distribution.Update(value);
+ return value;
+ }
+
+ public int ReadLiteral(int bitCount)
+ {
+ const uint prob = (0x7FFFFFU - (128 << 15) + 128) >> 8;
+ int literal = 0;
+ for (int bit = bitCount - 1; bit >= 0; bit--)
+ {
+ if (this.DecodeBoolQ15(prob))
+ {
+ literal |= 1 << bit;
+ }
+ }
+
+ return literal;
+ }
+
+ ///
+ /// Decode a single binary value.
+ ///
+ /// The probability that the bit is one, scaled by 32768.
+ private bool DecodeBoolQ15(uint frequency)
+ {
+ uint dif;
+ uint vw;
+ uint range;
+ uint newRange;
+ uint v;
+ bool ret;
+
+ // assert(0 < f);
+ // assert(f < 32768U);
+ dif = this.difference;
+ range = this.range;
+
+ // assert(dif >> (DecoderWindowsSize - 16) < r);
+ // assert(32768U <= r);
+ v = ((range >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift);
+ v += Av1Distribution.ProbabilityMinimum;
+ vw = v << (DecoderWindowsSize - 16);
+ ret = true;
+ newRange = v;
+ if (dif >= vw)
+ {
+ newRange = range - v;
+ dif -= vw;
+ ret = false;
+ }
+
+ this.Normalize(dif, newRange);
+ return ret;
+ }
+
+ ///
+ /// Decodes a symbol given an inverse cumulative distribution function(CDF) table in Q15.
+ ///
+ ///
+ /// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range
+ /// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]).
+ /// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0.
+ ///
+ /// The decoded symbol.
+ private int DecodeIntegerQ15(Av1Distribution distribution)
+ {
+ uint c;
+ uint u;
+ uint v;
+ int ret;
+
+ uint dif = this.difference;
+ uint r = this.range;
+ int n = distribution.NumberOfSymbols - 1;
+
+ DebugGuard.MustBeLessThan(dif >> (DecoderWindowsSize - 16), r, nameof(r));
+ DebugGuard.IsTrue(distribution[n] == 0, "Last value in probability array needs to be zero.");
+ DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r));
+ DebugGuard.MustBeGreaterThanOrEqualTo(7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift, 0, nameof(Av1Distribution.CdfShift));
+ c = dif >> (DecoderWindowsSize - 16);
+ v = r;
+ ret = -1;
+ do
+ {
+ u = v;
+ v = ((r >> 8) * (distribution[++ret] >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift);
+ v += (uint)(Av1Distribution.ProbabilityMinimum * (n - ret));
+ }
+ while (c < v);
+
+ DebugGuard.MustBeLessThan(v, u, nameof(v));
+ DebugGuard.MustBeLessThanOrEqualTo(u, r, nameof(u));
+ r = u - v;
+ dif -= v << (DecoderWindowsSize - 16);
+ this.Normalize(dif, r);
+ return ret;
+ }
+
+ ///
+ /// Takes updated dif and range values, renormalizes them so that
+ /// has value between 32768 and 65536 (reading more bytes from the stream into dif if
+ /// necessary), and stores them back in the decoder context.
+ ///
+ private void Normalize(uint dif, uint rng)
+ {
+ int d;
+
+ // assert(rng <= 65535U);
+ /*The number of leading zeros in the 16-bit binary representation of rng.*/
+ d = 15 - Av1Math.MostSignificantBit(rng);
+ /*d bits in dec->dif are consumed.*/
+ this.count -= d;
+ /*This is equivalent to shifting in 1's instead of 0's.*/
+ this.difference = ((dif + 1) << d) - 1;
+ this.range = rng << d;
+ if (this.count < 0)
+ {
+ this.Refill();
+ }
+ }
+
+ private void Refill()
+ {
+ int s;
+ uint dif = this.difference;
+ int cnt = this.count;
+ int position = this.position;
+ int end = this.buffer.Length;
+ s = DecoderWindowsSize - 9 - (cnt + 15);
+ for (; s >= 0 && position < end; s -= 8, position++)
+ {
+ /*Each time a byte is inserted into the window (dif), bptr advances and cnt
+ is incremented by 8, so the total number of consumed bits (the return
+ value of od_ec_dec_tell) does not change.*/
+ DebugGuard.MustBeLessThan(s, DecoderWindowsSize - 8, nameof(s));
+ dif ^= (uint)this.buffer[position] << s;
+ cnt += 8;
+ }
+
+ if (position >= end)
+ {
+ /*
+ * We've reached the end of the buffer. It is perfectly valid for us to need
+ * to fill the window with additional bits past the end of the buffer (and
+ * this happens in normal operation). These bits should all just be taken
+ * as zero. But we cannot increment bptr past 'end' (this is undefined
+ * behavior), so we start to increment dec->tell_offs. We also don't want
+ * to keep testing bptr against 'end', so we set cnt to OD_EC_LOTS_OF_BITS
+ * and adjust dec->tell_offs so that the total number of unconsumed bits in
+ * the window (dec->cnt - dec->tell_offs) does not change. This effectively
+ * puts lots of zero bits into the window, and means we won't try to refill
+ * it from the buffer for a very long time (at which point we'll put lots
+ * of zero bits into the window again).
+ */
+ cnt = LotsOfBits;
+ }
+
+ this.difference = dif;
+ this.count = cnt;
+ this.position = position;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs
new file mode 100644
index 0000000000..8765561800
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs
@@ -0,0 +1,211 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1SymbolWriter : IDisposable
+{
+ private uint low;
+ private uint rng = 0x8000U;
+
+ // Count is initialized to -9 so that it crosses zero after we've accumulated one byte + one carry bit.
+ private int cnt = -9;
+ private readonly Configuration configuration;
+ private readonly AutoExpandingMemory memory;
+ private int position;
+
+ public Av1SymbolWriter(Configuration configuration, int initialSize)
+ {
+ this.configuration = configuration;
+ this.memory = new AutoExpandingMemory(configuration, (initialSize + 1) >> 1);
+ }
+
+ public void Dispose() => this.memory.Dispose();
+
+ public void WriteSymbol(int symbol, Av1Distribution distribution)
+ {
+ DebugGuard.MustBeGreaterThanOrEqualTo(symbol, 0, nameof(symbol));
+ DebugGuard.MustBeLessThan(symbol, distribution.NumberOfSymbols, nameof(symbol));
+ DebugGuard.IsTrue(distribution[distribution.NumberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero.");
+
+ this.EncodeIntegerQ15(symbol, distribution);
+ distribution.Update(symbol);
+ }
+
+ public void WriteLiteral(uint value, int bitCount)
+ {
+ const uint p = 0x4000U; // (0x7FFFFFU - (128 << 15) + 128) >> 8;
+ for (int bit = bitCount - 1; bit >= 0; bit--)
+ {
+ bool bitValue = ((value >> bit) & 0x1) > 0;
+ this.EncodeBoolQ15(bitValue, p);
+ }
+ }
+
+ public IMemoryOwner Exit()
+ {
+ // We output the minimum number of bits that ensures that the symbols encoded
+ // thus far will be decoded correctly regardless of the bits that follow.
+ uint l = this.low;
+ int c = this.cnt;
+ int pos = this.position;
+ int s = 10;
+ uint m = 0x3FFFU;
+ uint e = ((l + m) & ~m) | (m + 1);
+ s += c;
+ Span buffer = this.memory.GetSpan(this.position + ((s + 7) >> 3));
+ if (s > 0)
+ {
+ uint n = (1U << (c + 16)) - 1;
+ do
+ {
+ buffer[pos] = (ushort)(e >> (c + 16));
+ pos++;
+ e &= n;
+ s -= 8;
+ c -= 8;
+ n >>= 8;
+ }
+ while (s > 0);
+ }
+
+ c = Math.Max((s + 7) >> 3, 0);
+ IMemoryOwner output = this.configuration.MemoryAllocator.Allocate(pos + c);
+
+ // Perform carry propagation.
+ Span outputSlice = output.GetSpan()[(output.Length() - pos)..];
+ c = 0;
+ while (pos > 0)
+ {
+ pos--;
+ c = buffer[pos] + c;
+ outputSlice[pos] = (byte)c;
+ c >>= 8;
+ }
+
+ return output;
+ }
+
+ ///
+ /// Encode a single binary value.
+ ///
+ /// The value to encode.
+ /// The probability that the value is true, scaled by 32768.
+ private void EncodeBoolQ15(bool val, uint frequency)
+ {
+ uint l;
+ uint r;
+ uint v;
+ DebugGuard.MustBeGreaterThan(frequency, 0U, nameof(frequency));
+ DebugGuard.MustBeLessThanOrEqualTo(frequency, 32768U, nameof(frequency));
+ l = this.low;
+ r = this.rng;
+ DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r));
+ v = ((r >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift);
+ v += Av1Distribution.ProbabilityMinimum;
+ if (val)
+ {
+ l += r - v;
+ r = v;
+ }
+ else
+ {
+ r -= v;
+ }
+
+ this.Normalize(l, r);
+ }
+
+ ///
+ /// Encodes a symbol given an inverse cumulative distribution function(CDF) table in Q15.
+ ///
+ /// The value to encode.
+ ///
+ /// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range
+ /// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]).
+ /// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0.
+ ///
+ private void EncodeIntegerQ15(int symbol, Av1Distribution distribution)
+ => this.EncodeIntegerQ15(symbol > 0 ? distribution[symbol - 1] : Av1Distribution.ProbabilityTop, distribution[symbol], symbol, distribution.NumberOfSymbols);
+
+ private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, int numberOfSymbols)
+ {
+ const int totalShift = 7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift;
+ uint l = this.low;
+ uint r = this.rng;
+ DebugGuard.MustBeLessThanOrEqualTo(32768U, r, nameof(r));
+ DebugGuard.MustBeLessThanOrEqualTo(highFrequency, lowFrequency, nameof(highFrequency));
+ DebugGuard.MustBeLessThanOrEqualTo(lowFrequency, 32768U, nameof(lowFrequency));
+ DebugGuard.MustBeGreaterThanOrEqualTo(totalShift, 0, nameof(totalShift));
+ int n = numberOfSymbols - 1;
+ if (lowFrequency < Av1Distribution.ProbabilityTop)
+ {
+ uint u;
+ uint v;
+ u = (uint)((((r >> 8) * (lowFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) +
+ (Av1Distribution.ProbabilityMinimum * (n - (symbol - 1))));
+ v = (uint)((((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) +
+ (Av1Distribution.ProbabilityMinimum * (n - symbol)));
+ l += r - u;
+ r = u - v;
+ }
+ else
+ {
+ r -= (uint)((((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) +
+ (Av1Distribution.ProbabilityMinimum * (n - symbol)));
+ }
+
+ this.Normalize(l, r);
+ }
+
+ ///
+ /// Takes updated low and range values, renormalizes them so that
+ /// lies between 32768 and 65536 (flushing bytes from low to the pre-carry buffer if necessary),
+ /// and stores them back in the encoder context.
+ ///
+ /// The new value of .
+ /// The new value of .
+ private void Normalize(uint low, uint rng)
+ {
+ int d;
+ int c;
+ int s;
+ c = this.cnt;
+ DebugGuard.MustBeLessThanOrEqualTo(rng, 65535U, nameof(rng));
+ d = 15 - Av1Math.MostSignificantBit(rng);
+ s = c + d;
+ /*TODO: Right now we flush every time we have at least one byte available.
+ Instead we should use an OdEcWindow and flush right before we're about to
+ shift bits off the end of the window.
+ For a 32-bit window this is about the same amount of work, but for a 64-bit
+ window it should be a fair win.*/
+ if (s >= 0)
+ {
+ uint m;
+ Span buffer = this.memory.GetSpan(this.position + 2);
+
+ c += 16;
+ m = (1U << c) - 1;
+ if (s >= 8)
+ {
+ buffer[this.position] = (ushort)(low >> c);
+ this.position++;
+ low &= m;
+ c -= 8;
+ m >>= 8;
+ }
+
+ buffer[this.position] = (ushort)(low >> c);
+ this.position++;
+ s = c + d - 24;
+ low &= m;
+ }
+
+ this.low = low << d;
+ this.rng = rng << d;
+ this.cnt = s;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs
new file mode 100644
index 0000000000..4a5e503c83
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1TileInfo
+{
+ public Av1TileInfo(int row, int column, ObuFrameHeader frameHeader)
+ {
+ this.SetTileRow(frameHeader.TilesInfo, frameHeader.ModeInfoRowCount, row);
+ this.SetTileColumn(frameHeader.TilesInfo, frameHeader.ModeInfoColumnCount, column);
+ }
+
+ public int ModeInfoRowStart { get; private set; }
+
+ public int ModeInfoRowEnd { get; private set; }
+
+ public int ModeInfoColumnStart { get; private set; }
+
+ public int ModeInfoColumnEnd { get; private set; }
+
+ public Point TileIndex { get; private set; }
+
+ public int TileIndexInRasterOrder { get; }
+
+ public void SetTileRow(ObuTileGroupHeader tileGroupHeader, int modeInfoRowCount, int row)
+ {
+ this.ModeInfoRowStart = tileGroupHeader.TileRowStartModeInfo[row];
+ this.ModeInfoRowEnd = Math.Min(tileGroupHeader.TileRowStartModeInfo[row + 1], modeInfoRowCount);
+ Point loc = this.TileIndex;
+ loc.Y = row;
+ this.TileIndex = loc;
+ }
+
+ public void SetTileColumn(ObuTileGroupHeader tileGroupHeader, int modeInfoColumnCount, int column)
+ {
+ this.ModeInfoColumnStart = tileGroupHeader.TileColumnStartModeInfo[column];
+ this.ModeInfoColumnEnd = Math.Min(tileGroupHeader.TileColumnStartModeInfo[column + 1], modeInfoColumnCount);
+ Point loc = this.TileIndex;
+ loc.X = column;
+ this.TileIndex = loc;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
new file mode 100644
index 0000000000..48f45eceeb
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
@@ -0,0 +1,1933 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1;
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1TileReader : IAv1TileReader
+{
+ private static readonly int[] SgrprojXqdMid = [-32, 31];
+ private static readonly int[] WienerTapsMid = [3, -7, 15];
+ private const int PartitionProbabilitySet = 4;
+ private static readonly int[] Signs = [0, -1, 1];
+ private static readonly int[] DcSignContexts = [
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
+
+ private static readonly int[][] SkipContexts = [
+ [1, 2, 2, 2, 3], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 6]];
+
+ private static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513];
+ private static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+
+ private int[][] referenceSgrXqd = [];
+ private int[][][] referenceLrWiener = [];
+ private readonly Av1ParseAboveNeighbor4x4Context aboveNeighborContext;
+ private readonly Av1ParseLeftNeighbor4x4Context leftNeighborContext;
+ private int currentQuantizerIndex;
+ private readonly int[][] segmentIds = [];
+ private readonly int[][] transformUnitCount;
+ private readonly int[] firstTransformOffset = new int[2];
+ private readonly int[] coefficientIndex = [];
+ private readonly Configuration configuration;
+
+ public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
+ {
+ this.FrameHeader = frameHeader;
+ this.configuration = configuration;
+ this.SequenceHeader = sequenceHeader;
+
+ // init_main_frame_ctxt
+ this.FrameInfo = new(this.SequenceHeader);
+ this.segmentIds = new int[this.FrameHeader.ModeInfoRowCount][];
+ for (int y = 0; y < this.FrameHeader.ModeInfoRowCount; y++)
+ {
+ this.segmentIds[y] = new int[this.FrameHeader.ModeInfoColumnCount];
+ }
+
+ // reallocate_parse_context_memory
+ // Hard code number of threads to 1 for now.
+ int planesCount = sequenceHeader.ColorConfig.PlaneCount;
+ int superblockColumnCount =
+ Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperblockSizeLog2) >> sequenceHeader.SuperblockSizeLog2;
+ int modeInfoWideColumnCount = superblockColumnCount * sequenceHeader.SuperblockModeInfoSize;
+ modeInfoWideColumnCount = Av1Math.AlignPowerOf2(modeInfoWideColumnCount, sequenceHeader.SuperblockSizeLog2 - 2);
+ this.aboveNeighborContext = new Av1ParseAboveNeighbor4x4Context(planesCount, modeInfoWideColumnCount);
+ this.leftNeighborContext = new Av1ParseLeftNeighbor4x4Context(planesCount, sequenceHeader.SuperblockModeInfoSize);
+ this.transformUnitCount = new int[Av1Constants.MaxPlanes][];
+ this.transformUnitCount[0] = new int[this.FrameInfo.ModeInfoCount];
+ this.transformUnitCount[1] = new int[this.FrameInfo.ModeInfoCount];
+ this.transformUnitCount[2] = new int[this.FrameInfo.ModeInfoCount];
+ this.coefficientIndex = new int[Av1Constants.MaxPlanes];
+ }
+
+ public ObuFrameHeader FrameHeader { get; }
+
+ public ObuSequenceHeader SequenceHeader { get; }
+
+ public Av1FrameInfo FrameInfo { get; }
+
+ public void ReadTile(Span tileData, int tileNum)
+ {
+ Av1SymbolDecoder reader = new(tileData, this.FrameHeader.QuantizationParameters.BaseQIndex);
+ int tileColumnIndex = tileNum % this.FrameHeader.TilesInfo.TileColumnCount;
+ int tileRowIndex = tileNum / this.FrameHeader.TilesInfo.TileColumnCount;
+
+ int modeInfoColumnStart = this.FrameHeader.TilesInfo.TileColumnStartModeInfo[tileColumnIndex];
+ int modeInfoColumnEnd = this.FrameHeader.TilesInfo.TileColumnStartModeInfo[tileColumnIndex + 1];
+ int modeInfoRowStart = this.FrameHeader.TilesInfo.TileRowStartModeInfo[tileRowIndex];
+ int modeInfoRowEnd = this.FrameHeader.TilesInfo.TileRowStartModeInfo[tileRowIndex + 1];
+ this.aboveNeighborContext.Clear(this.SequenceHeader, modeInfoColumnStart, modeInfoColumnEnd);
+ this.ClearLoopFilterDelta();
+ int planesCount = this.SequenceHeader.ColorConfig.PlaneCount;
+
+ // Default initialization of Wiener and SGR Filter.
+ this.referenceSgrXqd = new int[planesCount][];
+ this.referenceLrWiener = new int[planesCount][][];
+ for (int plane = 0; plane < planesCount; plane++)
+ {
+ this.referenceSgrXqd[plane] = new int[2];
+ Array.Copy(SgrprojXqdMid, this.referenceSgrXqd[plane], SgrprojXqdMid.Length);
+ this.referenceLrWiener[plane] = new int[2][];
+ for (int pass = 0; pass < 2; pass++)
+ {
+ this.referenceLrWiener[plane][pass] = new int[Av1Constants.WienerCoefficientCount];
+ Array.Copy(WienerTapsMid, this.referenceLrWiener[plane][pass], WienerTapsMid.Length);
+ }
+ }
+
+ Av1TileInfo tileInfo = new(tileRowIndex, tileColumnIndex, this.FrameHeader);
+ Av1BlockSize superBlockSize = this.SequenceHeader.SuperblockSize;
+ int superBlock4x4Size = this.SequenceHeader.SuperblockSizeLog2;
+ for (int row = modeInfoRowStart; row < modeInfoRowEnd; row += superBlock4x4Size)
+ {
+ int superBlockRow = (row << Av1Constants.ModeInfoSizeLog2) >> superBlock4x4Size;
+ this.leftNeighborContext.Clear(this.SequenceHeader);
+ for (int column = modeInfoColumnStart; column < modeInfoColumnEnd; column += superBlock4x4Size)
+ {
+ int superBlockColumn = (column << Av1Constants.ModeInfoSizeLog2) >> superBlock4x4Size;
+ Point superblockPosition = new(superBlockColumn, superBlockRow);
+ Av1SuperblockInfo superblockInfo = this.FrameInfo.GetSuperblock(superblockPosition);
+
+ Point modeInfoPosition = new(column, row);
+ this.FrameInfo.ClearCdef(superblockPosition);
+ this.firstTransformOffset[0] = 0;
+ this.firstTransformOffset[1] = 0;
+ this.ReadLoopRestoration(modeInfoPosition, superBlockSize);
+ this.ParsePartition(ref reader, modeInfoPosition, superBlockSize, superblockInfo, tileInfo);
+ }
+ }
+ }
+
+ private void ClearLoopFilterDelta()
+ => this.FrameInfo.ClearDeltaLoopFilter();
+
+ private void ReadLoopRestoration(Point modeInfoLocation, Av1BlockSize superBlockSize)
+ {
+ int planesCount = this.SequenceHeader.ColorConfig.PlaneCount;
+ for (int plane = 0; plane < planesCount; plane++)
+ {
+ if (this.FrameHeader.LoopRestorationParameters.Items[plane].Type != ObuRestorationType.None)
+ {
+ // TODO: Implement.
+ throw new NotImplementedException("No loop restoration filter support.");
+ }
+ }
+ }
+
+ ///
+ /// 5.11.4. Decode partition syntax.
+ ///
+ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
+ {
+ int columnIndex = modeInfoLocation.X;
+ int rowIndex = modeInfoLocation.Y;
+ if (modeInfoLocation.Y >= this.FrameHeader.ModeInfoRowCount || modeInfoLocation.X >= this.FrameHeader.ModeInfoColumnCount)
+ {
+ return;
+ }
+
+ int block4x4Size = blockSize.Get4x4WideCount();
+ int halfBlock4x4Size = block4x4Size >> 1;
+ int quarterBlock4x4Size = halfBlock4x4Size >> 1;
+ bool hasRows = (modeInfoLocation.Y + halfBlock4x4Size) < this.FrameHeader.ModeInfoRowCount;
+ bool hasColumns = (modeInfoLocation.X + halfBlock4x4Size) < this.FrameHeader.ModeInfoColumnCount;
+ Av1PartitionType partitionType = Av1PartitionType.None;
+ if (blockSize >= Av1BlockSize.Block8x8)
+ {
+ int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo);
+ partitionType = Av1PartitionType.Split;
+ if (blockSize < Av1BlockSize.Block8x8)
+ {
+ partitionType = Av1PartitionType.None;
+ }
+ else if (hasRows && hasColumns)
+ {
+ partitionType = reader.ReadPartitionType(ctx);
+ }
+ else if (hasColumns)
+ {
+ bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx);
+ partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Horizontal;
+ }
+ else if (hasRows)
+ {
+ bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx);
+ partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Vertical;
+ }
+ }
+
+ Av1BlockSize subSize = partitionType.GetBlockSubSize(blockSize);
+ Av1BlockSize splitSize = Av1PartitionType.Split.GetBlockSubSize(blockSize);
+ switch (partitionType)
+ {
+ case Av1PartitionType.Split:
+ Point loc1 = new(modeInfoLocation.X, modeInfoLocation.Y + halfBlock4x4Size);
+ Point loc2 = new(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y);
+ Point loc3 = new(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y + halfBlock4x4Size);
+ this.ParsePartition(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo);
+ this.ParsePartition(ref reader, loc1, subSize, superblockInfo, tileInfo);
+ this.ParsePartition(ref reader, loc2, subSize, superblockInfo, tileInfo);
+ this.ParsePartition(ref reader, loc3, subSize, superblockInfo, tileInfo);
+ break;
+ case Av1PartitionType.None:
+ this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.None);
+ break;
+ case Av1PartitionType.Horizontal:
+ this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Horizontal);
+ if (hasRows)
+ {
+ Point halfLocation = new(columnIndex, rowIndex + halfBlock4x4Size);
+ this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Horizontal);
+ }
+
+ break;
+ case Av1PartitionType.Vertical:
+ this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Vertical);
+ if (hasColumns)
+ {
+ Point halfLocation = new(columnIndex + halfBlock4x4Size, rowIndex);
+ this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Vertical);
+ }
+
+ break;
+ case Av1PartitionType.HorizontalA:
+ this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalA);
+ Point locHorA1 = new(columnIndex + halfBlock4x4Size, rowIndex);
+ this.ParseBlock(ref reader, locHorA1, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalA);
+ Point locHorA2 = new(columnIndex, rowIndex + halfBlock4x4Size);
+ this.ParseBlock(ref reader, locHorA2, subSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalA);
+ break;
+ case Av1PartitionType.HorizontalB:
+ this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB);
+ Point locHorB1 = new(columnIndex, rowIndex + halfBlock4x4Size);
+ this.ParseBlock(ref reader, locHorB1, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB);
+ Point locHorB2 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size);
+ this.ParseBlock(ref reader, locHorB2, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB);
+ break;
+ case Av1PartitionType.VerticalA:
+ this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalA);
+ Point locVertA1 = new(columnIndex, rowIndex + halfBlock4x4Size);
+ this.ParseBlock(ref reader, locVertA1, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalA);
+ Point locVertA2 = new(columnIndex + halfBlock4x4Size, rowIndex);
+ this.ParseBlock(ref reader, locVertA2, subSize, superblockInfo, tileInfo, Av1PartitionType.VerticalA);
+ break;
+ case Av1PartitionType.VerticalB:
+ this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB);
+ Point locVertB1 = new(columnIndex + halfBlock4x4Size, rowIndex);
+ this.ParseBlock(ref reader, locVertB1, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB);
+ Point locVertB2 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size);
+ this.ParseBlock(ref reader, locVertB2, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB);
+ break;
+ case Av1PartitionType.Horizontal4:
+ for (int i = 0; i < 4; i++)
+ {
+ int currentBlockRow = rowIndex + (i * quarterBlock4x4Size);
+ if (i > 0 && currentBlockRow > this.FrameHeader.ModeInfoRowCount)
+ {
+ break;
+ }
+
+ Point currentLocation = new(modeInfoLocation.X, currentBlockRow);
+ this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Horizontal4);
+ }
+
+ break;
+ case Av1PartitionType.Vertical4:
+ for (int i = 0; i < 4; i++)
+ {
+ int currentBlockColumn = columnIndex + (i * quarterBlock4x4Size);
+ if (i > 0 && currentBlockColumn > this.FrameHeader.ModeInfoColumnCount)
+ {
+ break;
+ }
+
+ Point currentLocation = new(currentBlockColumn, modeInfoLocation.Y);
+ this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Vertical4);
+ }
+
+ break;
+ default:
+ throw new NotImplementedException($"Partition type: {partitionType} is not supported.");
+ }
+
+ this.UpdatePartitionContext(new Point(columnIndex, rowIndex), tileInfo, superblockInfo, subSize, blockSize, partitionType);
+ }
+
+ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1PartitionType partitionType)
+ {
+ int rowIndex = modeInfoLocation.Y;
+ int columnIndex = modeInfoLocation.X;
+ int block4x4Width = blockSize.Get4x4WideCount();
+ int block4x4Height = blockSize.Get4x4HighCount();
+ int planesCount = this.SequenceHeader.ColorConfig.PlaneCount;
+ int subX = this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0;
+ int subY = this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0;
+ Point superblockLocation = superblockInfo.Position * this.SequenceHeader.SuperblockModeInfoSize;
+ Point locationInSuperblock = new Point(modeInfoLocation.X - superblockLocation.X, modeInfoLocation.Y - superblockLocation.Y);
+ Av1BlockModeInfo blockModeInfo = new(planesCount, blockSize, locationInSuperblock);
+ blockModeInfo.PartitionType = partitionType;
+ blockModeInfo.FirstTransformLocation[0] = this.firstTransformOffset[0];
+ blockModeInfo.FirstTransformLocation[1] = this.firstTransformOffset[1];
+ bool hasChroma = HasChroma(this.SequenceHeader, modeInfoLocation, blockSize);
+ Av1PartitionInfo partitionInfo = new(blockModeInfo, superblockInfo, hasChroma, partitionType);
+ partitionInfo.ColumnIndex = columnIndex;
+ partitionInfo.RowIndex = rowIndex;
+ superblockInfo.BlockCount++;
+ partitionInfo.ComputeBoundaryOffsets(this.configuration, this.SequenceHeader, this.FrameHeader, tileInfo);
+ if (hasChroma)
+ {
+ if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1)
+ {
+ partitionInfo.AvailableAboveForChroma = this.IsInside(rowIndex - 2, columnIndex);
+ }
+
+ if (this.SequenceHeader.ColorConfig.SubSamplingX && block4x4Width == 1)
+ {
+ partitionInfo.AvailableLeftForChroma = this.IsInside(rowIndex, columnIndex - 2);
+ }
+ }
+
+ if (partitionInfo.AvailableAbove)
+ {
+ partitionInfo.AboveModeInfo = superblockInfo.GetModeInfo(new Point(rowIndex - 1, columnIndex));
+ }
+
+ if (partitionInfo.AvailableLeft)
+ {
+ partitionInfo.LeftModeInfo = superblockInfo.GetModeInfo(new Point(rowIndex, columnIndex - 1));
+ }
+
+ if (partitionInfo.AvailableAboveForChroma)
+ {
+ partitionInfo.AboveModeInfoForChroma = superblockInfo.GetModeInfo(new Point(rowIndex & ~subY, columnIndex | subX));
+ }
+
+ if (partitionInfo.AvailableLeftForChroma)
+ {
+ partitionInfo.LeftModeInfoForChroma = superblockInfo.GetModeInfo(new Point(rowIndex | subY, columnIndex & ~subX));
+ }
+
+ this.ReadModeInfo(ref reader, partitionInfo);
+ ReadPaletteTokens(ref reader, partitionInfo);
+ this.ReadBlockTransformSize(ref reader, modeInfoLocation, partitionInfo, superblockInfo, tileInfo);
+ if (partitionInfo.ModeInfo.Skip)
+ {
+ this.ResetSkipContext(partitionInfo);
+ }
+
+ this.Residual(ref reader, partitionInfo, superblockInfo, tileInfo, blockSize);
+
+ // Update the Frame buffer for this ModeInfo.
+ this.FrameInfo.UpdateModeInfo(blockModeInfo, superblockInfo);
+ }
+
+ private void ResetSkipContext(Av1PartitionInfo partitionInfo)
+ {
+ int planesCount = this.SequenceHeader.ColorConfig.PlaneCount;
+ for (int i = 0; i < planesCount; i++)
+ {
+ int subX = (i > 0 && this.SequenceHeader.ColorConfig.SubSamplingX) ? 1 : 0;
+ int subY = (i > 0 && this.SequenceHeader.ColorConfig.SubSamplingY) ? 1 : 0;
+ Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY);
+ DebugGuard.IsTrue(planeBlockSize != Av1BlockSize.Invalid, nameof(planeBlockSize));
+ int txsWide = planeBlockSize.GetWidth() >> 2;
+ int txsHigh = planeBlockSize.GetHeight() >> 2;
+ int aboveOffset = (partitionInfo.ColumnIndex - this.FrameHeader.TilesInfo.TileColumnStartModeInfo[partitionInfo.ColumnIndex]) >> subX;
+ int leftOffset = (partitionInfo.RowIndex - this.FrameHeader.TilesInfo.TileRowStartModeInfo[partitionInfo.RowIndex]) >> subY;
+ this.aboveNeighborContext.ClearContext(i, aboveOffset, txsWide);
+ this.leftNeighborContext.ClearContext(i, leftOffset, txsHigh);
+ }
+ }
+
+ ///
+ /// 5.11.34. Residual syntax.
+ ///
+ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1BlockSize blockSize)
+ {
+ int maxBlocksWide = partitionInfo.GetMaxBlockWide(blockSize, false);
+ int maxBlocksHigh = partitionInfo.GetMaxBlockHigh(blockSize, false);
+ Av1BlockSize maxUnitSize = Av1BlockSize.Block64x64;
+ int modeUnitBlocksWide = maxUnitSize.GetWidth() >> 2;
+ int modeUnitBlocksHigh = maxUnitSize.GetHeight() >> 2;
+ modeUnitBlocksWide = Math.Min(maxBlocksWide, modeUnitBlocksWide);
+ modeUnitBlocksHigh = Math.Min(maxBlocksHigh, modeUnitBlocksHigh);
+ int planeCount = this.SequenceHeader.ColorConfig.PlaneCount;
+ bool isLossless = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId];
+ bool isLosslessBlock = isLossless && (blockSize >= Av1BlockSize.Block64x64) && (blockSize <= Av1BlockSize.Block128x128);
+ int subSampling = (this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0) + (this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0);
+ int chromaTransformUnitCount = isLosslessBlock ? ((maxBlocksWide * maxBlocksHigh) >> subSampling) : partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Uv];
+
+ int[] transformInfoIndices = new int[3];
+ transformInfoIndices[0] = superblockInfo.TransformInfoIndexY + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y];
+ transformInfoIndices[1] = superblockInfo.TransformInfoIndexUv + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv];
+ transformInfoIndices[2] = transformInfoIndices[1] + chromaTransformUnitCount;
+ int forceSplitCount = 0;
+
+ for (int row = 0; row < maxBlocksHigh; row += modeUnitBlocksHigh)
+ {
+ for (int column = 0; column < maxBlocksWide; column += modeUnitBlocksWide)
+ {
+ for (int plane = 0; plane < planeCount; ++plane)
+ {
+ int totalTransformUnitCount;
+ int transformUnitCount;
+ int subX = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX) ? 1 : 0;
+ int subY = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY) ? 1 : 0;
+
+ if (plane != 0 && !partitionInfo.IsChroma)
+ {
+ continue;
+ }
+
+ ref Av1TransformInfo transformInfoRef = ref (plane == 0) ? ref superblockInfo.GetTransformInfoY() : ref superblockInfo.GetTransformInfoUv();
+ if (isLosslessBlock)
+ {
+ // TODO: Implement.
+ int unitHeight = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksHigh + row, maxBlocksHigh), 0);
+ int unitWidth = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksWide + column, maxBlocksWide), 0);
+ DebugGuard.IsTrue(Unsafe.Add(ref transformInfoRef, transformInfoIndices[plane]).Size == Av1TransformSize.Size4x4, "Lossless frame shall have transform units of size 4x4.");
+ transformUnitCount = ((unitWidth - column) * (unitHeight - row)) >> (subX + subY);
+ }
+ else
+ {
+ totalTransformUnitCount = partitionInfo.ModeInfo.TransformUnitsCount[Math.Min(1, plane)];
+ transformUnitCount = this.transformUnitCount[plane][forceSplitCount];
+
+ DebugGuard.IsFalse(totalTransformUnitCount == 0, nameof(totalTransformUnitCount), string.Empty);
+ DebugGuard.IsTrue(
+ totalTransformUnitCount ==
+ this.transformUnitCount[plane][0] + this.transformUnitCount[plane][1] +
+ this.transformUnitCount[plane][2] + this.transformUnitCount[plane][3],
+ nameof(totalTransformUnitCount),
+ string.Empty);
+ }
+
+ DebugGuard.IsFalse(transformUnitCount == 0, nameof(transformUnitCount), string.Empty);
+ for (int tu = 0; tu < transformUnitCount; tu++)
+ {
+ Av1TransformInfo transformInfo = Unsafe.Add(ref transformInfoRef, transformInfoIndices[plane]);
+ DebugGuard.MustBeLessThanOrEqualTo(transformInfo.OffsetX, maxBlocksWide, nameof(transformInfo));
+ DebugGuard.MustBeLessThanOrEqualTo(transformInfo.OffsetY, maxBlocksHigh, nameof(transformInfo));
+
+ int coefficientIndex = this.coefficientIndex[plane];
+ int endOfBlock = 0;
+ int blockColumn = transformInfo.OffsetX;
+ int blockRow = transformInfo.OffsetY;
+ int startX = (partitionInfo.ColumnIndex >> subX) + blockColumn;
+ int startY = (partitionInfo.RowIndex >> subY) + blockRow;
+
+ if (startX >= (this.FrameHeader.ModeInfoColumnCount >> subX) ||
+ startY >= (this.FrameHeader.ModeInfoRowCount >> subY))
+ {
+ return;
+ }
+
+ if (!partitionInfo.ModeInfo.Skip)
+ {
+ endOfBlock = this.ParseTransformBlock(ref reader, partitionInfo, coefficientIndex, transformInfo, plane, blockColumn, blockRow, startX, startY, transformInfo.Size, subX != 0, subY != 0);
+ }
+
+ if (endOfBlock != 0)
+ {
+ this.coefficientIndex[plane] += endOfBlock + 1;
+ transformInfo.CodeBlockFlag = true;
+ }
+ else
+ {
+ transformInfo.CodeBlockFlag = false;
+ }
+
+ transformInfoIndices[plane]++;
+ }
+ }
+
+ forceSplitCount++;
+ }
+ }
+ }
+
+ public static bool HasChroma(ObuSequenceHeader sequenceHeader, Point modeInfoLocation, Av1BlockSize blockSize)
+ {
+ int blockWide = blockSize.Get4x4WideCount();
+ int blockHigh = blockSize.Get4x4HighCount();
+ bool subX = sequenceHeader.ColorConfig.SubSamplingX;
+ bool subY = sequenceHeader.ColorConfig.SubSamplingY;
+ bool hasChroma = ((modeInfoLocation.Y & 0x01) != 0 || (blockHigh & 0x01) == 0 || !subY) &&
+ ((modeInfoLocation.X & 0x01) != 0 || (blockWide & 0x01) == 0 || !subX);
+ return hasChroma;
+ }
+
+ private Av1TransformSize GetSize(int plane, object transformSize) => throw new NotImplementedException();
+
+ ///
+ /// 5.11.38. Get plane residual size function.
+ /// The GetPlaneResidualSize returns the size of a residual block for the specified plane. (The residual block will always
+ /// have width and height at least equal to 4.)
+ ///
+ private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane)
+ {
+ bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX;
+ bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY;
+ bool subX = plane > 0 && subsamplingX;
+ bool subY = plane > 0 && subsamplingY;
+ return sizeChunk.GetSubsampled(subX, subY);
+ }
+
+ ///
+ /// 5.11.35. Transform block syntax.
+ ///
+ ///
+ /// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification.
+ ///
+ private int ParseTransformBlock(
+ ref Av1SymbolDecoder reader,
+ Av1PartitionInfo partitionInfo,
+ int coefficientIndex,
+ Av1TransformInfo transformInfo,
+ int plane,
+ int blockColumn,
+ int blockRow,
+ int startX,
+ int startY,
+ Av1TransformSize transformSize,
+ bool subX,
+ bool subY)
+ {
+ int endOfBlock = 0;
+ Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY);
+ int transformBlockUnitWideCount = transformSize.Get4x4WideCount();
+ int transformBlockUnitHighCount = transformSize.Get4x4HighCount();
+
+ if (partitionInfo.ModeBlockToRightEdge < 0)
+ {
+ int blocksWide = partitionInfo.GetMaxBlockWide(planeBlockSize, subX);
+ transformBlockUnitWideCount = Math.Min(transformBlockUnitWideCount, blocksWide - blockColumn);
+ }
+
+ if (partitionInfo.ModeBlockToBottomEdge < 0)
+ {
+ int blocksHigh = partitionInfo.GetMaxBlockHigh(planeBlockSize, subY);
+ transformBlockUnitHighCount = Math.Min(transformBlockUnitHighCount, blocksHigh - blockRow);
+ }
+
+ Av1TransformBlockContext transformBlockContext = this.GetTransformBlockContext(transformSize, plane, planeBlockSize, transformBlockUnitHighCount, transformBlockUnitWideCount, startY, startX);
+ endOfBlock = this.ParseCoefficients(ref reader, partitionInfo, startY, startX, blockRow, blockColumn, plane, transformBlockContext, transformSize, coefficientIndex, transformInfo);
+
+ return endOfBlock;
+ }
+
+ ///
+ /// 5.11.39. Coefficients syntax.
+ ///
+ ///
+ /// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification.
+ ///
+ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, int blockRow, int blockColumn, int aboveOffset, int leftOffset, int plane, Av1TransformBlockContext transformBlockContext, Av1TransformSize transformSize, int coefficientIndex, Av1TransformInfo transformInfo)
+ {
+ Span coefficientBuffer = this.FrameInfo.GetCoefficients(plane);
+ int width = transformSize.GetWidth();
+ int height = transformSize.GetHeight();
+ Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1);
+ Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1);
+ int culLevel = 0;
+ int dcValue = 0;
+
+ int[] levelsBuffer = new int[Av1Constants.TransformPad2d];
+ Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..];
+
+ bool allZero = reader.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext);
+ int bwl = transformSize.GetBlockWidthLog2();
+ int endOfBlock;
+ int maxScanLine = 0;
+ if (allZero)
+ {
+ if (plane == 0)
+ {
+ transformInfo.Type = Av1TransformType.DctDct;
+ transformInfo.CodeBlockFlag = false;
+ }
+
+ this.UpdateCoefficientContext(plane, partitionInfo, transformSize, blockRow, blockColumn, aboveOffset, leftOffset, culLevel);
+ return 0;
+ }
+
+ int endOfBlockExtra = 0;
+ int endOfBlockPoint = 0;
+
+ transformInfo.Type = this.ComputeTransformType(planeType, partitionInfo, transformSize, transformInfo);
+ Av1TransformClass transformClass = transformInfo.Type.ToClass();
+ Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformInfo.Type);
+ Span scan = scanOrder.Scan;
+
+ endOfBlockPoint = reader.ReadEndOfBlockFlag(planeType, transformClass, transformSize);
+ int endOfBlockShift = EndOfBlockOffsetBits[endOfBlockPoint];
+ if (endOfBlockShift > 0)
+ {
+ int endOfBlockContext = endOfBlockPoint;
+ bool bit = reader.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext);
+ if (bit)
+ {
+ endOfBlockExtra += 1 << (endOfBlockShift - 1);
+ }
+ else
+ {
+ for (int j = 1; j < endOfBlockShift; j++)
+ {
+ if (reader.ReadLiteral(1) != 0)
+ {
+ endOfBlockExtra += 1 << (endOfBlockShift - 1 - j);
+ }
+ }
+ }
+ }
+
+ endOfBlock = RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra);
+ if (endOfBlock > 1)
+ {
+ Array.Fill(levelsBuffer, 0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd);
+ }
+
+ int i = endOfBlock - 1;
+ int pos = scan[i];
+ int coefficientContext = GetLowerLevelContextEndOfBlock(bwl, height, i);
+ int level = reader.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext);
+ if (level > Av1Constants.BaseLevelsCount)
+ {
+ int baseRangeContext = GetBaseRangeContextEndOfBlock(pos, bwl, transformClass);
+ for (int idx = 0; idx < Av1Constants.CoefficientBaseRange / Av1Constants.BaseRangeSizeMinus1; idx++)
+ {
+ int coefficinetBaseRange = reader.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext);
+ level += coefficinetBaseRange;
+ if (coefficinetBaseRange < Av1Constants.BaseRangeSizeMinus1)
+ {
+ break;
+ }
+ }
+ }
+
+ levels[GetPaddedIndex(pos, bwl)] = level;
+
+ if (endOfBlock > 1)
+ {
+ if (transformClass == Av1TransformClass.Class2D)
+ {
+ ReadCoefficientsReverse2d(ref reader, transformSize, 1, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType);
+ ReadCoefficientsReverse(ref reader, transformSize, transformInfo.Type, 0, 0, scan, bwl, levels, transformSizeContext, planeType);
+ }
+ else
+ {
+ ReadCoefficientsReverse(ref reader, transformSize, transformInfo.Type, 0, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType);
+ }
+ }
+
+ DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan));
+ coefficientBuffer[0] = endOfBlock;
+ for (int c = 0; c < endOfBlock; c++)
+ {
+ int sign = 0;
+ level = levels[GetPaddedIndex(scan[c], bwl)];
+ if (level != 0)
+ {
+ maxScanLine = Math.Max(maxScanLine, scan[c]);
+ if (c == 0)
+ {
+ sign = reader.ReadDcSign(planeType, transformBlockContext.DcSignContext);
+ }
+ else
+ {
+ sign = reader.ReadLiteral(1);
+ }
+
+ if (level >= Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount + 1)
+ {
+ level += ReadGolomb(ref reader);
+ }
+
+ if (c == 0)
+ {
+ dcValue = sign != 0 ? -level : level;
+ }
+
+ level &= 0xfffff;
+ culLevel += level;
+ }
+
+ coefficientBuffer[c + 1] = sign != 0 ? -level : level;
+ }
+
+ culLevel = Math.Min(Av1Constants.CoefficientContextMask, culLevel);
+ SetDcSign(ref culLevel, dcValue);
+
+ this.UpdateCoefficientContext(plane, partitionInfo, transformSize, blockRow, blockColumn, aboveOffset, leftOffset, culLevel);
+
+ transformInfo.CodeBlockFlag = true;
+ return endOfBlock;
+ }
+
+ private static void ReadCoefficientsReverse2d(ref Av1SymbolDecoder reader, Av1TransformSize transformSize, int startSi, int endSi, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
+ {
+ for (int c = endSi; c >= startSi; --c)
+ {
+ int pos = scan[c];
+ int coefficientContext = GetLowerLevelsContext2d(levels, pos, bwl, transformSize);
+ int level = reader.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType);
+ if (level > Av1Constants.BaseLevelsCount)
+ {
+ int baseRangeContext = GetBaseRangeContext2d(levels, pos, bwl);
+ for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1)
+ {
+ int k = reader.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext);
+ level += k;
+ if (k < Av1Constants.BaseRangeSizeMinus1)
+ {
+ break;
+ }
+ }
+ }
+
+ levels[GetPaddedIndex(pos, bwl)] = level;
+ }
+ }
+
+ private static int GetBaseRangeContext2d(Span levels, int c, int bwl)
+ {
+ DebugGuard.MustBeGreaterThan(c, 0, nameof(c));
+ int row = c >> bwl;
+ int col = c - (row << bwl);
+ int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal;
+ int pos = (row * stride) + col;
+ int mag =
+ Math.Min(levels[pos + 1], Av1Constants.MaxBaseRange) +
+ Math.Min(levels[pos + stride], Av1Constants.MaxBaseRange) +
+ Math.Min(levels[pos + 1 + stride], Av1Constants.MaxBaseRange);
+ mag = Math.Min((mag + 1) >> 1, 6);
+ if ((row | col) < 2)
+ {
+ return mag + 7;
+ }
+
+ return mag + 14;
+ }
+
+ private static int GetLowerLevelsContext2d(Span levels, int pos, int bwl, Av1TransformSize transformSize)
+ {
+ DebugGuard.MustBeGreaterThan(pos, 0, nameof(pos));
+ int mag;
+ levels = levels[GetPaddedIndex(pos, bwl)..];
+ mag = Math.Min(levels[1], 3); // { 0, 1 }
+ mag += Math.Min(levels[(1 << bwl) + Av1Constants.TransformPadHorizontal], 3); // { 1, 0 }
+ mag += Math.Min(levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1], 3); // { 1, 1 }
+ mag += Math.Min(levels[2], 3); // { 0, 2 }
+ mag += Math.Min(levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)], 3); // { 2, 0 }
+
+ int ctx = Math.Min((mag + 1) >> 1, 4);
+ return ctx + Av1NzMap.GetNzMapContext(transformSize, pos);
+ }
+
+ private static void ReadCoefficientsReverse(ref Av1SymbolDecoder reader, Av1TransformSize transformSize, Av1TransformType transformType, int startSi, int endSi, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
+ {
+ Av1TransformClass transformClass = transformType.ToClass();
+ for (int c = endSi; c >= startSi; --c)
+ {
+ int pos = scan[c];
+ int coefficientContext = GetLowerLevelsContext(levels, pos, bwl, transformSize, transformClass);
+ int level = reader.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType);
+ if (level > Av1Constants.BaseLevelsCount)
+ {
+ int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass);
+ for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1)
+ {
+ int k = reader.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext);
+ level += k;
+ if (k < Av1Constants.BaseRangeSizeMinus1)
+ {
+ break;
+ }
+ }
+ }
+
+ levels[GetPaddedIndex(pos, bwl)] = level;
+ }
+ }
+
+ private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1TransformClass transformClass)
+ {
+ int row = c >> bwl;
+ int col = c - (row << bwl);
+ int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal;
+ int pos = (row * stride) + col;
+ int mag = levels[pos + 1];
+ mag += levels[pos + stride];
+ switch (transformClass)
+ {
+ case Av1TransformClass.Class2D:
+ mag += levels[pos + stride + 1];
+ mag = Math.Min((mag + 1) >> 1, 6);
+ if (c == 0)
+ {
+ return mag;
+ }
+
+ if ((row < 2) && (col < 2))
+ {
+ return mag + 7;
+ }
+
+ break;
+ case Av1TransformClass.ClassHorizontal:
+ mag += levels[pos + 2];
+ mag = Math.Min((mag + 1) >> 1, 6);
+ if (c == 0)
+ {
+ return mag;
+ }
+
+ if (col == 0)
+ {
+ return mag + 7;
+ }
+
+ break;
+ case Av1TransformClass.ClassVertical:
+ mag += levels[pos + (stride << 1)];
+ mag = Math.Min((mag + 1) >> 1, 6);
+ if (c == 0)
+ {
+ return mag;
+ }
+
+ if (row == 0)
+ {
+ return mag + 7;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ return mag + 14;
+ }
+
+ private static int GetLowerLevelsContext(Span levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass)
+ {
+ int stats = Av1NzMap.GetNzMagnitude(levels[GetPaddedIndex(pos, bwl)..], bwl, transformClass);
+ return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass);
+ }
+
+ private static int ReadGolomb(ref Av1SymbolDecoder reader)
+ {
+ int x = 1;
+ int length = 0;
+ int i = 0;
+
+ while (i == 0)
+ {
+ i = reader.ReadLiteral(1);
+ ++length;
+ if (length > 20)
+ {
+ // SVT_LOG("Invalid length in read_golomb");
+ break;
+ }
+ }
+
+ for (i = 0; i < length - 1; ++i)
+ {
+ x <<= 1;
+ x += reader.ReadLiteral(1);
+ }
+
+ return x - 1;
+ }
+
+ private static void SetDcSign(ref int culLevel, int dcValue)
+ {
+ if (dcValue < 0)
+ {
+ culLevel |= 1 << Av1Constants.CoefficientContextBitCount;
+ }
+ else if (dcValue > 0)
+ {
+ culLevel += 2 << Av1Constants.CoefficientContextBitCount;
+ }
+ }
+
+ private static int GetPaddedIndex(int scanIndex, int bwl)
+ => scanIndex + ((scanIndex >> bwl) << Av1Constants.TransformPadHorizontalLog2);
+
+ private static int GetBaseRangeContextEndOfBlock(int pos, int bwl, Av1TransformClass transformClass)
+ {
+ int row = pos >> bwl;
+ int col = pos - (row << bwl);
+ if (pos == 0)
+ {
+ return 0;
+ }
+
+ if ((transformClass == Av1TransformClass.Class2D && row < 2 && col < 2) ||
+ (transformClass == Av1TransformClass.ClassHorizontal && col == 0) ||
+ (transformClass == Av1TransformClass.ClassVertical && row == 0))
+ {
+ return 7;
+ }
+
+ return 14;
+ }
+
+ private static int GetLowerLevelContextEndOfBlock(int bwl, int height, int scanIndex)
+ {
+ if (scanIndex == 0)
+ {
+ return 0;
+ }
+
+ if (scanIndex <= (height << bwl) >> 3)
+ {
+ return 1;
+ }
+
+ if (scanIndex <= (height << bwl) >> 2)
+ {
+ return 2;
+ }
+
+ return 3;
+ }
+
+ private void UpdateCoefficientContext(int plane, Av1PartitionInfo partitionInfo, Av1TransformSize transformSize, int blockRow, int blockColumn, int aboveOffset, int leftOffset, int culLevel)
+ {
+ bool subX = this.SequenceHeader.ColorConfig.SubSamplingX;
+ bool subY = this.SequenceHeader.ColorConfig.SubSamplingY;
+ int[] aboveContexts = this.aboveNeighborContext.GetContext(plane);
+ int[] leftContexts = this.leftNeighborContext.GetContext(plane);
+ int transformSizeWide = transformSize.Get4x4WideCount();
+ int transformSizeHigh = transformSize.Get4x4HighCount();
+
+ if (partitionInfo.ModeBlockToRightEdge < 0)
+ {
+ Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY);
+ int blocksWide = partitionInfo.GetMaxBlockWide(planeBlockSize, subX);
+ int aboveContextCount = Math.Min(transformSizeWide, blocksWide - aboveOffset);
+ Array.Fill(aboveContexts, culLevel, 0, aboveContextCount);
+ Array.Fill(aboveContexts, 0, aboveContextCount, transformSizeWide - aboveContextCount);
+ }
+ else
+ {
+ Array.Fill(aboveContexts, culLevel, 0, transformSizeWide);
+ }
+
+ if (partitionInfo.ModeBlockToBottomEdge < 0)
+ {
+ Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY);
+ int blocksHigh = partitionInfo.GetMaxBlockHigh(planeBlockSize, subY);
+ int leftContextCount = Math.Min(transformSizeHigh, blocksHigh - leftOffset);
+ Array.Fill(leftContexts, culLevel, 0, leftContextCount);
+ Array.Fill(leftContexts, 0, leftContextCount, transformSizeWide - leftContextCount);
+ }
+ else
+ {
+ Array.Fill(leftContexts, culLevel, 0, transformSizeHigh);
+ }
+ }
+
+ private static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra)
+ {
+ int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint];
+ if (endOfBlock > 2)
+ {
+ endOfBlock += endOfBlockExtra;
+ }
+
+ return endOfBlock;
+ }
+
+ private Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1PartitionInfo partitionInfo, Av1TransformSize transformSize, Av1TransformInfo transformInfo)
+ {
+ Av1TransformType transformType = Av1TransformType.DctDct;
+ if (this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId] || transformSize.GetSquareUpSize() > Av1TransformSize.Size32x32)
+ {
+ transformType = Av1TransformType.DctDct;
+ }
+ else
+ {
+ if (planeType == Av1PlaneType.Y)
+ {
+ transformType = transformInfo.Type;
+ }
+ else
+ {
+ // In intra mode, uv planes don't share the same prediction mode as y
+ // plane, so the tx_type should not be shared
+ transformType = ConvertIntraModeToTransformType(partitionInfo.ModeInfo, Av1PlaneType.Uv);
+ }
+ }
+
+ Av1TransformSetType transformSetType = GetExtendedTransformSetType(transformSize, this.FrameHeader.UseReducedTransformSet);
+ if (!transformType.IsExtendedSetUsed(transformSetType))
+ {
+ transformType = Av1TransformType.DctDct;
+ }
+
+ return transformType;
+ }
+
+ private static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet)
+ {
+ Av1TransformSize squareUpSize = transformSize.GetSquareUpSize();
+
+ if (squareUpSize >= Av1TransformSize.Size32x32)
+ {
+ return Av1TransformSetType.DctOnly;
+ }
+
+ if (useReducedSet)
+ {
+ return Av1TransformSetType.Dtt4Identity;
+ }
+
+ Av1TransformSize squareSize = transformSize.GetSquareSize();
+ return squareSize == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct;
+ }
+
+ private static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType)
+ {
+ Av1PredictionMode mode = (planeType == Av1PlaneType.Y) ? modeInfo.YMode : modeInfo.UvMode;
+ if (mode == Av1PredictionMode.UvChromaFromLuma)
+ {
+ mode = Av1PredictionMode.DC;
+ }
+
+ return mode.ToTransformType();
+ }
+
+ private Av1TransformBlockContext GetTransformBlockContext(Av1TransformSize transformSize, int plane, Av1BlockSize planeBlockSize, int transformBlockUnitHighCount, int transformBlockUnitWideCount, int startY, int startX)
+ {
+ Av1TransformBlockContext transformBlockContext = new();
+ int[] aboveContext = this.aboveNeighborContext.GetContext(plane);
+ int[] leftContext = this.leftNeighborContext.GetContext(plane);
+ int dcSign = 0;
+ int k = 0;
+ int mask = (1 << Av1Constants.CoefficientContextBitCount) - 1;
+
+ do
+ {
+ uint sign = (uint)aboveContext[k] >> Av1Constants.CoefficientContextBitCount;
+ DebugGuard.MustBeLessThanOrEqualTo(sign, 2U, nameof(sign));
+ dcSign += Signs[sign];
+ }
+ while (++k < transformBlockUnitWideCount);
+
+ k = 0;
+ do
+ {
+ uint sign = (uint)leftContext[k] >> Av1Constants.CoefficientContextBitCount;
+ DebugGuard.MustBeLessThanOrEqualTo(sign, 2U, nameof(sign));
+ dcSign += Signs[sign];
+ }
+ while (++k < transformBlockUnitHighCount);
+
+ transformBlockContext.DcSignContext = DcSignContexts[dcSign + (Av1Constants.MaxTransformSizeUnit << 1)];
+
+ if (plane == 0)
+ {
+ if (planeBlockSize == transformSize.ToBlockSize())
+ {
+ transformBlockContext.SkipContext = 0;
+ }
+ else
+ {
+ int top = 0;
+ int left = 0;
+
+ k = 0;
+ do
+ {
+ top |= aboveContext[k];
+ }
+ while (++k < transformBlockUnitWideCount);
+ top &= mask;
+
+ k = 0;
+ do
+ {
+ left |= leftContext[k];
+ }
+ while (++k < transformBlockUnitHighCount);
+ left &= mask;
+
+ int max = Math.Min(top | left, 4);
+ int min = Math.Min(Math.Min(top, left), 4);
+
+ transformBlockContext.SkipContext = SkipContexts[min][max];
+ }
+ }
+ else
+ {
+ int contextBase = GetEntropyContext(transformSize, aboveContext, leftContext);
+ int contextOffset = planeBlockSize.GetPelsLog2Count() > transformSize.ToBlockSize().GetPelsLog2Count() ? 10 : 7;
+ transformBlockContext.SkipContext = contextBase + contextOffset;
+ }
+
+ return transformBlockContext;
+ }
+
+ private static int GetEntropyContext(Av1TransformSize transformSize, int[] above, int[] left)
+ {
+ bool aboveEntropyContext = false;
+ bool leftEntropyContext = false;
+
+ switch (transformSize)
+ {
+ case Av1TransformSize.Size4x4:
+ aboveEntropyContext = above[0] != 0;
+ leftEntropyContext = left[0] != 0;
+ break;
+ case Av1TransformSize.Size4x8:
+ aboveEntropyContext = above[0] != 0;
+ leftEntropyContext = (left[0] & (left[1] << 8)) != 0; // !!*(const uint16_t*)left;
+ break;
+ case Av1TransformSize.Size8x4:
+ aboveEntropyContext = (above[0] & (above[1] << 8)) != 0; // !!*(const uint16_t*)above;
+ leftEntropyContext = left[0] != 0;
+ break;
+ case Av1TransformSize.Size8x16:
+ aboveEntropyContext = (above[0] & (above[1] << 8)) != 0; // !!*(const uint16_t*)above;
+ leftEntropyContext = (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0; // !!*(const uint32_t*)left;
+ break;
+ case Av1TransformSize.Size16x8:
+ aboveEntropyContext = (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0; // !!*(const uint32_t*)above;
+ leftEntropyContext = (left[0] & (left[1] << 8)) != 0; // !!*(const uint16_t*)left;
+ break;
+ case Av1TransformSize.Size16x32:
+ aboveEntropyContext = (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0; // !!*(const uint32_t*)above;
+ leftEntropyContext =
+ (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 ||
+ (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0; // !!*(const uint64_t*)left;
+ break;
+ case Av1TransformSize.Size32x16:
+ aboveEntropyContext =
+ (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 ||
+ (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0; // !!*(const uint64_t*)above;
+ leftEntropyContext = (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0; // !!*(const uint32_t*)left;
+ break;
+ case Av1TransformSize.Size8x8:
+ aboveEntropyContext = (above[0] & (above[1] << 8)) != 0; // !!*(const uint16_t*)above;
+ leftEntropyContext = (left[0] & (left[1] << 8)) != 0; // !!*(const uint16_t*)left;
+ break;
+ case Av1TransformSize.Size16x16:
+ aboveEntropyContext = (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0; // !!*(const uint32_t*)above;
+ leftEntropyContext = (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0; // !!*(const uint32_t*)left;
+ break;
+ case Av1TransformSize.Size32x32:
+ aboveEntropyContext =
+ (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 ||
+ (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0; // !!*(const uint64_t*)above;
+ leftEntropyContext =
+ (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 ||
+ (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0; // !!*(const uint64_t*)left;
+ break;
+ case Av1TransformSize.Size64x64:
+ aboveEntropyContext =
+ (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 ||
+ (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0 ||
+ (above[8] & (above[9] << 8) & (above[10] << 16) & (above[11] << 24)) != 0 ||
+ (above[12] & (above[13] << 8) & (above[14] << 16) & (above[15] << 24)) != 0; // !!(*(const uint64_t*)above | *(const uint64_t*)(above + 8));
+ leftEntropyContext =
+ (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 ||
+ (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0 ||
+ (left[8] & (left[9] << 8) & (left[10] << 16) & (left[11] << 24)) != 0 ||
+ (left[12] & (left[13] << 8) & (left[14] << 16) & (left[15] << 24)) != 0; // !!(*(const uint64_t*)left | *(const uint64_t*)(left + 8));
+ break;
+ case Av1TransformSize.Size32x64:
+ aboveEntropyContext =
+ (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 ||
+ (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0; // !!*(const uint64_t*)above;
+ leftEntropyContext =
+ (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 ||
+ (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0 ||
+ (left[8] & (left[9] << 8) & (left[10] << 16) & (left[11] << 24)) != 0 ||
+ (left[12] & (left[13] << 8) & (left[14] << 16) & (left[15] << 24)) != 0; // !!(*(const uint64_t*)left | *(const uint64_t*)(left + 8));
+ break;
+ case Av1TransformSize.Size64x32:
+ aboveEntropyContext =
+ (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 ||
+ (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0 ||
+ (above[8] & (above[9] << 8) & (above[10] << 16) & (above[11] << 24)) != 0 ||
+ (above[12] & (above[13] << 8) & (above[14] << 16) & (above[15] << 24)) != 0; // !!(*(const uint64_t*)above | *(const uint64_t*)(above + 8));
+ leftEntropyContext =
+ (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 ||
+ (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0; // !!*(const uint64_t*)left;
+ break;
+ case Av1TransformSize.Size4x16:
+ aboveEntropyContext = above[0] != 0;
+ leftEntropyContext = (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0; // !!*(const uint32_t*)left;
+ break;
+ case Av1TransformSize.Size16x4:
+ aboveEntropyContext = (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0; // !!*(const uint32_t*)above;
+ leftEntropyContext = left[0] != 0;
+ break;
+ case Av1TransformSize.Size8x32:
+ aboveEntropyContext = (above[0] & (above[1] << 8)) != 0; // !!*(const uint16_t*)above;
+ leftEntropyContext =
+ (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 ||
+ (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0; // !!*(const uint64_t*)left;
+ break;
+ case Av1TransformSize.Size32x8:
+ aboveEntropyContext =
+ (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 ||
+ (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0; // !!*(const uint64_t*)above;
+ leftEntropyContext = (left[0] & (left[1] << 8)) != 0; // !!*(const uint16_t*)left;
+ break;
+ case Av1TransformSize.Size16x64:
+ aboveEntropyContext = (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0; // !!*(const uint32_t*)above;
+ leftEntropyContext =
+ (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 ||
+ (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0 ||
+ (left[8] & (left[9] << 8) & (left[10] << 16) & (left[11] << 24)) != 0 ||
+ (left[12] & (left[13] << 8) & (left[14] << 16) & (left[15] << 24)) != 0; // !!(*(const uint64_t*)left | *(const uint64_t*)(left + 8));
+ break;
+ case Av1TransformSize.Size64x16:
+ aboveEntropyContext =
+ (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 ||
+ (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0 ||
+ (above[8] & (above[9] << 8) & (above[10] << 16) & (above[11] << 24)) != 0 ||
+ (above[12] & (above[13] << 8) & (above[14] << 16) & (above[15] << 24)) != 0; // !!(*(const uint64_t*)above | *(const uint64_t*)(above + 8));
+ leftEntropyContext = (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0; // !!*(const uint32_t*)left;
+ break;
+ default:
+ Guard.IsTrue(false, nameof(transformSize), "Invalid transform size.");
+ break;
+ }
+
+ return (aboveEntropyContext ? 1 : 0) + (leftEntropyContext ? 1 : 0);
+ }
+
+ ///
+ /// 5.11.15. TX size syntax.
+ ///
+ private Av1TransformSize ReadTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, bool allowSelect)
+ {
+ Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo;
+ if (this.FrameHeader.LosslessArray[modeInfo.SegmentId])
+ {
+ return Av1TransformSize.Size4x4;
+ }
+
+ if (modeInfo.BlockSize > Av1BlockSize.Block4x4 && allowSelect && this.FrameHeader.TransformMode == Av1TransformMode.Select)
+ {
+ return this.ReadSelectedTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo);
+ }
+
+ return modeInfo.BlockSize.GetMaximumTransformSize();
+ }
+
+ private Av1TransformSize ReadSelectedTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
+ {
+ int context = 0;
+ Av1TransformSize maxTransformSize = partitionInfo.ModeInfo.BlockSize.GetMaximumTransformSize();
+ int aboveWidth = this.aboveNeighborContext.AboveTransformWidth[partitionInfo.ColumnIndex - tileInfo.ModeInfoColumnStart];
+ int above = (aboveWidth >= maxTransformSize.GetWidth()) ? 1 : 0;
+ int leftHeight = this.leftNeighborContext.LeftTransformHeight[partitionInfo.RowIndex - superblockInfo.Position.Y];
+ int left = (leftHeight >= maxTransformSize.GetHeight()) ? 1 : 0;
+ bool hasAbove = partitionInfo.AvailableAbove;
+ bool hasLeft = partitionInfo.AvailableLeft;
+
+ if (hasAbove && hasLeft)
+ {
+ context = above + left;
+ }
+ else if (hasAbove)
+ {
+ context = above;
+ }
+ else if (hasLeft)
+ {
+ context = left;
+ }
+ else
+ {
+ context = 0;
+ }
+
+ return reader.ReadTransformSize(partitionInfo.ModeInfo.BlockSize, context);
+ }
+
+ ///
+ /// Section 5.11.16. Block TX size syntax.
+ ///
+ private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
+ {
+ Av1BlockSize blockSize = partitionInfo.ModeInfo.BlockSize;
+ int block4x4Width = blockSize.Get4x4WideCount();
+ int block4x4Height = blockSize.Get4x4HighCount();
+
+ // First condition in spec is for INTER frames, implemented only the INTRA condition.
+ Av1TransformSize transformSize = this.ReadTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo, true);
+ this.aboveNeighborContext.UpdateTransformation(modeInfoLocation, tileInfo, transformSize, blockSize, false);
+ this.leftNeighborContext.UpdateTransformation(modeInfoLocation, superblockInfo, transformSize, blockSize, false);
+ this.UpdateTransformInfo(partitionInfo, superblockInfo, blockSize, transformSize);
+ }
+
+ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1BlockSize blockSize, Av1TransformSize transformSize)
+ {
+ int transformInfoYIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y];
+ int transformInfoUvIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv];
+ ref Av1TransformInfo lumaTransformInfo = ref superblockInfo.GetTransformInfoY();
+ ref Av1TransformInfo chromaTransformInfo = ref superblockInfo.GetTransformInfoUv();
+ int totalLumaTransformUnitCount = 0;
+ int totalChromaTransformUnitCount = 0;
+ int forceSplitCount = 0;
+ bool subX = this.SequenceHeader.ColorConfig.SubSamplingX;
+ bool subY = this.SequenceHeader.ColorConfig.SubSamplingY;
+ int maxBlockWide = partitionInfo.GetMaxBlockWide(blockSize, false);
+ int maxBlockHigh = partitionInfo.GetMaxBlockHigh(blockSize, false);
+ int width = 64 >> 2;
+ int height = 64 >> 2;
+ width = Math.Min(width, maxBlockWide);
+ height = Math.Min(height, maxBlockHigh);
+
+ bool isLossLess = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId];
+ Av1TransformSize transformSizeUv = isLossLess ? Av1TransformSize.Size4x4 : blockSize.GetMaxUvTransformSize(subX, subY);
+
+ for (int idy = 0; idy < maxBlockHigh; idy += height)
+ {
+ for (int idx = 0; idx < maxBlockWide; idx += width, forceSplitCount++)
+ {
+ int lumaTransformUnitCount = 0;
+ int chromaTransformUnitCount = 0;
+
+ // Update Luminance Transform Info.
+ int stepColumn = transformSize.Get4x4WideCount();
+ int stepRow = transformSize.Get4x4HighCount();
+
+ int unitHeight = Av1Math.RoundPowerOf2(Math.Min(height + idy, maxBlockHigh), 0);
+ int unitWidth = Av1Math.RoundPowerOf2(Math.Min(width + idx, maxBlockWide), 0);
+ for (int blockRow = idy; blockRow < unitHeight; blockRow += stepRow)
+ {
+ for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn)
+ {
+ Unsafe.Add(ref lumaTransformInfo, transformInfoYIndex) = new Av1TransformInfo(
+ transformSize, blockColumn, blockRow);
+ transformInfoYIndex++;
+ lumaTransformUnitCount++;
+ totalLumaTransformUnitCount++;
+ }
+ }
+
+ this.transformUnitCount[(int)Av1Plane.Y][forceSplitCount] = lumaTransformUnitCount;
+
+ if (this.SequenceHeader.ColorConfig.IsMonochrome || !partitionInfo.IsChroma)
+ {
+ continue;
+ }
+
+ // Update Chroma Transform Info.
+ stepColumn = transformSizeUv.Get4x4WideCount();
+ stepRow = transformSizeUv.Get4x4HighCount();
+
+ unitHeight = Av1Math.RoundPowerOf2(Math.Min(height + idx, maxBlockHigh), subY ? 1 : 0);
+ unitWidth = Av1Math.RoundPowerOf2(Math.Min(width + idx, maxBlockWide), subX ? 1 : 0);
+ for (int blockRow = idy; blockRow < unitHeight; blockRow += stepRow)
+ {
+ for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn)
+ {
+ Unsafe.Add(ref chromaTransformInfo, transformInfoUvIndex) = new Av1TransformInfo(
+ transformSizeUv, blockColumn, blockRow);
+ transformInfoUvIndex++;
+ chromaTransformUnitCount++;
+ totalChromaTransformUnitCount++;
+ }
+ }
+
+ this.transformUnitCount[(int)Av1Plane.U][forceSplitCount] = chromaTransformUnitCount;
+ this.transformUnitCount[(int)Av1Plane.V][forceSplitCount] = chromaTransformUnitCount;
+ }
+ }
+
+ // Cr Transform Info Update from Cb.
+ if (totalChromaTransformUnitCount != 0)
+ {
+ DebugGuard.IsTrue(
+ (transformInfoUvIndex - totalChromaTransformUnitCount) ==
+ partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv],
+ nameof(totalChromaTransformUnitCount));
+ int originalIndex = transformInfoUvIndex - totalChromaTransformUnitCount;
+ ref Av1TransformInfo originalInfo = ref Unsafe.Add(ref chromaTransformInfo, originalIndex);
+ ref Av1TransformInfo infoV = ref Unsafe.Add(ref chromaTransformInfo, transformInfoUvIndex);
+ for (int i = 0; i < totalChromaTransformUnitCount; i++)
+ {
+ infoV = originalInfo;
+ originalInfo = ref Unsafe.Add(ref originalInfo, 1);
+ infoV = ref Unsafe.Add(ref infoV, 1);
+ }
+ }
+
+ partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Y] = totalLumaTransformUnitCount;
+ partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Uv] = totalChromaTransformUnitCount;
+
+ this.firstTransformOffset[(int)Av1PlaneType.Y] += totalLumaTransformUnitCount;
+ this.firstTransformOffset[(int)Av1PlaneType.Uv] += totalChromaTransformUnitCount << 1;
+ }
+
+ ///
+ /// 5.11.49. Palette tokens syntax.
+ ///
+ private static void ReadPaletteTokens(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
+ {
+ if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) != 0)
+ {
+ // TODO: Implement.
+ throw new NotImplementedException();
+ }
+
+ if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) != 0)
+ {
+ // TODO: Implement.
+ throw new NotImplementedException();
+ }
+ }
+
+ ///
+ /// 5.11.6. Mode info syntax.
+ ///
+ private void ReadModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
+ {
+ DebugGuard.IsTrue(this.FrameHeader.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame, "Only INTRA frames supported.");
+ this.ReadIntraFrameModeInfo(ref reader, partitionInfo);
+ }
+
+ ///
+ /// 5.11.7. Intra frame mode info syntax.
+ ///
+ private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
+ {
+ if (this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip)
+ {
+ this.IntraSegmentId(ref reader, partitionInfo);
+ }
+
+ // this.skipMode = false;
+ partitionInfo.ModeInfo.Skip = this.ReadSkip(ref reader, partitionInfo);
+ if (!this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip)
+ {
+ this.IntraSegmentId(ref reader, partitionInfo);
+ }
+
+ this.ReadCdef(ref reader, partitionInfo);
+
+ if (this.FrameHeader.DeltaQParameters.IsPresent)
+ {
+ this.ReadDeltaQuantizerIndex(ref reader, partitionInfo);
+ this.ReadDeltaLoopFilter(ref reader, partitionInfo);
+ }
+
+ partitionInfo.ReferenceFrame[0] = 0; // IntraFrame;
+ partitionInfo.ReferenceFrame[1] = -1; // None;
+ partitionInfo.ModeInfo.SetPaletteSizes(0, 0);
+ bool useIntraBlockCopy = false;
+ if (this.AllowIntraBlockCopy())
+ {
+ useIntraBlockCopy = reader.ReadUseIntraBlockCopy();
+ }
+
+ if (useIntraBlockCopy)
+ {
+ partitionInfo.ModeInfo.YMode = Av1PredictionMode.DC;
+ partitionInfo.ModeInfo.UvMode = Av1PredictionMode.DC;
+ }
+ else
+ {
+ // this.IsInter = false;
+ partitionInfo.ModeInfo.YMode = reader.ReadYMode(partitionInfo.AboveModeInfo, partitionInfo.LeftModeInfo);
+
+ // 5.11.42.Intra angle info luma syntax.
+ partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Y] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.YMode, partitionInfo.ModeInfo.BlockSize);
+ if (partitionInfo.IsChroma && !this.SequenceHeader.ColorConfig.IsMonochrome)
+ {
+ partitionInfo.ModeInfo.UvMode = reader.ReadIntraModeUv(partitionInfo.ModeInfo.YMode, this.IsChromaForLumaAllowed(partitionInfo));
+ if (partitionInfo.ModeInfo.UvMode == Av1PredictionMode.UvChromaFromLuma)
+ {
+ ReadChromaFromLumaAlphas(ref reader, partitionInfo.ModeInfo);
+ }
+
+ // 5.11.43.Intra angle info chroma syntax.
+ partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Uv] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.UvMode, partitionInfo.ModeInfo.BlockSize);
+ }
+ else
+ {
+ partitionInfo.ModeInfo.UvMode = Av1PredictionMode.DC;
+ }
+
+ if (partitionInfo.ModeInfo.BlockSize >= Av1BlockSize.Block8x8 &&
+ partitionInfo.ModeInfo.BlockSize.GetWidth() <= 64 &&
+ partitionInfo.ModeInfo.BlockSize.GetHeight() <= 64 &&
+ this.FrameHeader.AllowScreenContentTools)
+ {
+ this.PaletteModeInfo(ref reader, partitionInfo);
+ }
+
+ this.FilterIntraModeInfo(ref reader, partitionInfo);
+ }
+ }
+
+ private bool AllowIntraBlockCopy()
+ => (this.FrameHeader.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame) &&
+ (this.SequenceHeader.ForceScreenContentTools > 0) &&
+ this.FrameHeader.AllowIntraBlockCopy;
+
+ private bool IsChromaForLumaAllowed(Av1PartitionInfo partitionInfo)
+ {
+ if (this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId])
+ {
+ // In lossless, CfL is available when the partition size is equal to the
+ // transform size.
+ bool subX = this.SequenceHeader.ColorConfig.SubSamplingX;
+ bool subY = this.SequenceHeader.ColorConfig.SubSamplingY;
+ Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY);
+ return planeBlockSize == Av1BlockSize.Block4x4;
+ }
+
+ // Spec: CfL is available to luma partitions lesser than or equal to 32x32
+ return partitionInfo.ModeInfo.BlockSize.GetWidth() <= 32 && partitionInfo.ModeInfo.BlockSize.GetHeight() <= 32;
+ }
+
+ private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
+ {
+ if (this.SequenceHeader.EnableFilterIntra &&
+ partitionInfo.ModeInfo.YMode == Av1PredictionMode.DC &&
+ partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) == 0 &&
+ Math.Max(partitionInfo.ModeInfo.BlockSize.GetWidth(), partitionInfo.ModeInfo.BlockSize.GetHeight()) <= 32)
+ {
+ partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = reader.ReadUseFilterUltra(partitionInfo.ModeInfo.BlockSize);
+ if (partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra)
+ {
+ partitionInfo.ModeInfo.FilterIntraModeInfo.Mode = reader.ReadFilterUltraMode();
+ }
+ }
+ else
+ {
+ partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = false;
+ }
+ }
+
+ ///
+ /// 5.11.46. Palette mode info syntax.
+ ///
+ private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) =>
+
+ // TODO: Implement.
+ throw new NotImplementedException();
+
+ ///
+ /// 5.11.45. Read CFL alphas syntax.
+ ///
+ private static void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader, Av1BlockModeInfo modeInfo)
+ {
+ int jointSignPlus1 = reader.ReadChromFromLumaSign() + 1;
+ int index = 0;
+ if (jointSignPlus1 >= 3)
+ {
+ index = reader.ReadChromaFromLumaAlphaU(jointSignPlus1) << Av1Constants.ChromaFromLumaAlphabetSizeLog2;
+ }
+
+ if (jointSignPlus1 % 3 != 0)
+ {
+ index += reader.ReadChromaFromLumaAlphaV(jointSignPlus1);
+ }
+
+ modeInfo.ChromaFromLumaAlphaSign = jointSignPlus1 - 1;
+ modeInfo.ChromaFromLumaAlphaIndex = index;
+ }
+
+ ///
+ /// 5.11.42. and 5.11.43.
+ ///
+ private static int IntraAngleInfo(ref Av1SymbolDecoder reader, Av1PredictionMode mode, Av1BlockSize blockSize)
+ {
+ int angleDelta = 0;
+ if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(mode))
+ {
+ int symbol = reader.ReadAngleDelta(mode);
+ angleDelta = symbol - Av1Constants.MaxAngleDelta;
+ }
+
+ return angleDelta;
+ }
+
+ private static bool IsDirectionalMode(Av1PredictionMode mode)
+ => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees;
+
+ ///
+ /// 5.11.8. Intra segment ID syntax.
+ ///
+ private void IntraSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
+ {
+ if (this.FrameHeader.SegmentationParameters.Enabled)
+ {
+ this.ReadSegmentId(ref reader, partitionInfo);
+ }
+
+ int blockWidth4x4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount();
+ int blockHeight4x4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount();
+ int modeInfoCountX = Math.Min(this.FrameHeader.ModeInfoColumnCount - partitionInfo.ColumnIndex, blockWidth4x4);
+ int modeInfoCountY = Math.Min(this.FrameHeader.ModeInfoRowCount - partitionInfo.RowIndex, blockHeight4x4);
+ int segmentId = partitionInfo.ModeInfo.SegmentId;
+ for (int y = 0; y < modeInfoCountY; y++)
+ {
+ int[] segmentRow = this.segmentIds[partitionInfo.RowIndex + y];
+ for (int x = 0; x < modeInfoCountX; x++)
+ {
+ segmentRow[partitionInfo.ColumnIndex + x] = segmentId;
+ }
+ }
+ }
+
+ ///
+ /// 5.11.9. Read segment ID syntax.
+ ///
+ private void ReadSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
+ {
+ int predictor;
+ int prevUL = -1;
+ int prevU = -1;
+ int prevL = -1;
+ int columnIndex = partitionInfo.ColumnIndex;
+ int rowIndex = partitionInfo.RowIndex;
+ if (partitionInfo.AvailableAbove && partitionInfo.AvailableLeft)
+ {
+ prevUL = this.GetSegmentId(partitionInfo, rowIndex - 1, columnIndex - 1);
+ }
+
+ if (partitionInfo.AvailableAbove)
+ {
+ prevU = this.GetSegmentId(partitionInfo, rowIndex - 1, columnIndex);
+ }
+
+ if (partitionInfo.AvailableLeft)
+ {
+ prevU = this.GetSegmentId(partitionInfo, rowIndex, columnIndex - 1);
+ }
+
+ if (prevU == -1)
+ {
+ predictor = prevL == -1 ? 0 : prevL;
+ }
+ else if (prevL == -1)
+ {
+ predictor = prevU;
+ }
+ else
+ {
+ predictor = prevU == prevUL ? prevU : prevL;
+ }
+
+ if (partitionInfo.ModeInfo.Skip)
+ {
+ partitionInfo.ModeInfo.SegmentId = predictor;
+ }
+ else
+ {
+ int ctx = prevUL < 0 ? 0 /* Edge cases */
+ : prevUL == prevU && prevUL == prevL ? 2
+ : prevUL == prevU || prevUL == prevL || prevU == prevL ? 1 : 0;
+ int lastActiveSegmentId = this.FrameHeader.SegmentationParameters.LastActiveSegmentId;
+ partitionInfo.ModeInfo.SegmentId = NegativeDeinterleave(reader.ReadSegmentId(ctx), predictor, lastActiveSegmentId + 1);
+ }
+ }
+
+ private int GetSegmentId(Av1PartitionInfo partitionInfo, int rowIndex, int columnIndex)
+ {
+ int modeInfoOffset = (rowIndex * this.FrameHeader.ModeInfoColumnCount) + columnIndex;
+ int bw4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount();
+ int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount();
+ int xMin = Math.Min(this.FrameHeader.ModeInfoColumnCount - columnIndex, bw4);
+ int yMin = Math.Min(this.FrameHeader.ModeInfoRowCount - rowIndex, bh4);
+ int segmentId = Av1Constants.MaxSegmentCount - 1;
+ for (int y = 0; y < yMin; y++)
+ {
+ for (int x = 0; x < xMin; x++)
+ {
+ segmentId = Math.Min(segmentId, this.segmentIds[y][x]);
+ }
+ }
+
+ return segmentId;
+ }
+
+ private static int NegativeDeinterleave(int diff, int reference, int max)
+ {
+ if (reference == 0)
+ {
+ return diff;
+ }
+
+ if (reference >= max - 1)
+ {
+ return max - diff - 1;
+ }
+
+ if (2 * reference < max)
+ {
+ if (diff <= 2 * reference)
+ {
+ if ((diff & 1) > 0)
+ {
+ return reference + ((diff + 1) >> 1);
+ }
+ else
+ {
+ return reference - (diff >> 1);
+ }
+ }
+
+ return diff;
+ }
+ else
+ {
+ if (diff <= 2 * (max - reference - 1))
+ {
+ if ((diff & 1) > 0)
+ {
+ return reference + ((diff + 1) >> 1);
+ }
+ else
+ {
+ return reference - (diff >> 1);
+ }
+ }
+
+ return max - (diff + 1);
+ }
+ }
+
+ ///
+ /// 5.11.56. Read CDEF syntax.
+ ///
+ private void ReadCdef(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
+ {
+ if (partitionInfo.ModeInfo.Skip || this.FrameHeader.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameHeader.AllowIntraBlockCopy)
+ {
+ return;
+ }
+
+ int cdefSize4 = Av1BlockSize.Block64x64.Get4x4WideCount();
+ int cdefMask4 = ~(cdefSize4 - 1);
+ int r = partitionInfo.RowIndex & cdefMask4;
+ int c = partitionInfo.ColumnIndex & cdefMask4;
+ if (partitionInfo.CdefStrength[r][c] == -1)
+ {
+ partitionInfo.CdefStrength[r][c] = reader.ReadLiteral(this.FrameHeader.CdefParameters.BitCount);
+ if (this.SequenceHeader.SuperblockSize == Av1BlockSize.Block128x128)
+ {
+ int w4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount();
+ int h4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount();
+ for (int i = r; i < r + h4; i += cdefSize4)
+ {
+ for (int j = c; j < c + w4; j += cdefSize4)
+ {
+ partitionInfo.CdefStrength[i & cdefMask4][j & cdefMask4] = partitionInfo.CdefStrength[r][c];
+ }
+ }
+ }
+ }
+ }
+
+ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
+ {
+ Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
+ if (this.FrameHeader.DeltaLoopFilterParameters.IsPresent ||
+ (partitionInfo.ModeInfo.BlockSize == superBlockSize && partitionInfo.ModeInfo.Skip))
+ {
+ return;
+ }
+
+ if (this.FrameHeader.DeltaLoopFilterParameters.IsPresent)
+ {
+ int frameLoopFilterCount = 1;
+ if (this.FrameHeader.DeltaLoopFilterParameters.IsMulti)
+ {
+ frameLoopFilterCount = this.SequenceHeader.ColorConfig.PlaneCount > 1 ? Av1Constants.FrameLoopFilterCount : Av1Constants.FrameLoopFilterCount - 2;
+ }
+
+ Span currentDeltaLoopFilter = partitionInfo.SuperblockInfo.SuperblockDeltaLoopFilter;
+ for (int i = 0; i < frameLoopFilterCount; i++)
+ {
+ int deltaLoopFilterAbsolute = reader.ReadDeltaLoopFilterAbsolute();
+ if (deltaLoopFilterAbsolute == Av1Constants.DeltaLoopFilterSmall)
+ {
+ int deltaLoopFilterRemainingBits = reader.ReadLiteral(3) + 1;
+ int deltaLoopFilterAbsoluteBitCount = reader.ReadLiteral(deltaLoopFilterRemainingBits);
+ deltaLoopFilterAbsolute = deltaLoopFilterAbsoluteBitCount + (1 << deltaLoopFilterRemainingBits) + 1;
+ }
+
+ if (deltaLoopFilterAbsolute != 0)
+ {
+ bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0;
+ int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute;
+ int deltaLoopFilterResolution = this.FrameHeader.DeltaLoopFilterParameters.Resolution;
+ currentDeltaLoopFilter[i] = Av1Math.Clip3(-Av1Constants.MaxLoopFilter, Av1Constants.MaxLoopFilter, currentDeltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << deltaLoopFilterResolution));
+ }
+ }
+ }
+ }
+
+ private bool ReadSkip(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
+ {
+ int segmentId = partitionInfo.ModeInfo.SegmentId;
+ if (this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip &&
+ this.FrameHeader.SegmentationParameters.IsFeatureActive(segmentId, ObuSegmentationLevelFeature.Skip))
+ {
+ return true;
+ }
+ else
+ {
+ int aboveSkip = partitionInfo.AboveModeInfo != null && partitionInfo.AboveModeInfo.Skip ? 1 : 0;
+ int leftSkip = partitionInfo.LeftModeInfo != null && partitionInfo.LeftModeInfo.Skip ? 1 : 0;
+ return reader.ReadSkip(aboveSkip + leftSkip);
+ }
+ }
+
+ private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
+ {
+ Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
+ if (!this.FrameHeader.DeltaQParameters.IsPresent ||
+ (partitionInfo.ModeInfo.BlockSize == superBlockSize && partitionInfo.ModeInfo.Skip))
+ {
+ return;
+ }
+
+ if (partitionInfo.ModeInfo.BlockSize != this.SequenceHeader.SuperblockSize || !partitionInfo.ModeInfo.Skip)
+ {
+ int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute();
+ if (deltaQuantizerAbsolute == Av1Constants.DeltaQuantizerSmall)
+ {
+ int deltaQuantizerRemainingBits = reader.ReadLiteral(3) + 1;
+ int deltaQuantizerAbsoluteBitCount = reader.ReadLiteral(deltaQuantizerRemainingBits);
+ deltaQuantizerAbsolute = deltaQuantizerRemainingBits + (1 << deltaQuantizerRemainingBits) + 1;
+ }
+
+ if (deltaQuantizerAbsolute != 0)
+ {
+ bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0;
+ int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute;
+ int deltaQuantizerResolution = this.FrameHeader.DeltaQParameters.Resolution;
+ this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << deltaQuantizerResolution));
+ partitionInfo.SuperblockInfo.SuperblockDeltaQ = this.currentQuantizerIndex;
+ }
+ }
+ }
+
+ private bool IsInside(int rowIndex, int columnIndex) =>
+ columnIndex >= this.FrameHeader.TilesInfo.TileColumnCount &&
+ columnIndex < this.FrameHeader.TilesInfo.TileColumnCount &&
+ rowIndex >= this.FrameHeader.TilesInfo.TileRowCount &&
+ rowIndex < this.FrameHeader.TilesInfo.TileRowCount;
+
+ /*
+ private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blockMode, bool subSamplingX, bool subSamplingY)
+ {
+ int block4x4Width = blockMode.BlockSize.Get4x4WideCount();
+ int block4x4Height = blockMode.BlockSize.Get4x4HighCount();
+ bool xPos = (columnIndex & 0x1) > 0 || (block4x4Width & 0x1) > 0 || !subSamplingX;
+ bool yPos = (rowIndex & 0x1) > 0 || (block4x4Height & 0x1) > 0 || !subSamplingY;
+ return xPos && yPos;
+ }*/
+
+ private int GetPartitionPlaneContext(Point location, Av1BlockSize blockSize, Av1TileInfo tileInfo, Av1SuperblockInfo superblockInfo)
+ {
+ // Maximum partition point is 8x8. Offset the log value occordingly.
+ int aboveCtx = this.aboveNeighborContext.AbovePartitionWidth[location.X - tileInfo.ModeInfoColumnStart];
+ int leftCtx = this.leftNeighborContext.LeftPartitionHeight[(location.Y - superblockInfo.Position.Y) & Av1PartitionContext.Mask];
+ int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2();
+ int above = (aboveCtx >> blockSizeLog) & 0x1;
+ int left = (leftCtx >> blockSizeLog) & 0x1;
+ DebugGuard.IsTrue(blockSize.Get4x4WidthLog2() == blockSize.Get4x4HeightLog2(), "Blocks should be square");
+ DebugGuard.MustBeGreaterThanOrEqualTo(blockSizeLog, 0, nameof(blockSizeLog));
+ return ((left << 1) + above) + (blockSizeLog * PartitionProbabilitySet);
+ }
+
+ private void UpdatePartitionContext(Point modeInfoLocation, Av1TileInfo tileLoc, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize, Av1PartitionType partition)
+ {
+ if (blockSize >= Av1BlockSize.Block8x8)
+ {
+ int hbs = blockSize.Get4x4WideCount() / 2;
+ Av1BlockSize blockSize2 = Av1PartitionType.Split.GetBlockSubSize(blockSize);
+ switch (partition)
+ {
+ case Av1PartitionType.Split:
+ if (blockSize != Av1BlockSize.Block8x8)
+ {
+ break;
+ }
+
+ goto PARTITIONS;
+ case Av1PartitionType.None:
+ case Av1PartitionType.Horizontal:
+ case Av1PartitionType.Vertical:
+ case Av1PartitionType.Horizontal4:
+ case Av1PartitionType.Vertical4:
+ PARTITIONS:
+ this.aboveNeighborContext.UpdatePartition(modeInfoLocation, tileLoc, subSize, blockSize);
+ this.leftNeighborContext.UpdatePartition(modeInfoLocation, superblockInfo, subSize, blockSize);
+ break;
+ case Av1PartitionType.HorizontalA:
+ this.aboveNeighborContext.UpdatePartition(modeInfoLocation, tileLoc, blockSize2, subSize);
+ this.leftNeighborContext.UpdatePartition(modeInfoLocation, superblockInfo, blockSize2, subSize);
+ Point locHorizontalA = new(modeInfoLocation.X, modeInfoLocation.Y + hbs);
+ this.aboveNeighborContext.UpdatePartition(locHorizontalA, tileLoc, subSize, subSize);
+ this.leftNeighborContext.UpdatePartition(locHorizontalA, superblockInfo, subSize, subSize);
+ break;
+ case Av1PartitionType.HorizontalB:
+ this.aboveNeighborContext.UpdatePartition(modeInfoLocation, tileLoc, subSize, subSize);
+ this.leftNeighborContext.UpdatePartition(modeInfoLocation, superblockInfo, subSize, subSize);
+ Point locHorizontalB = new(modeInfoLocation.X, modeInfoLocation.Y + hbs);
+ this.aboveNeighborContext.UpdatePartition(locHorizontalB, tileLoc, blockSize2, subSize);
+ this.leftNeighborContext.UpdatePartition(locHorizontalB, superblockInfo, blockSize2, subSize);
+ break;
+ case Av1PartitionType.VerticalA:
+ this.aboveNeighborContext.UpdatePartition(modeInfoLocation, tileLoc, blockSize2, subSize);
+ this.leftNeighborContext.UpdatePartition(modeInfoLocation, superblockInfo, blockSize2, subSize);
+ Point locVerticalA = new(modeInfoLocation.X + hbs, modeInfoLocation.Y);
+ this.aboveNeighborContext.UpdatePartition(locVerticalA, tileLoc, subSize, subSize);
+ this.leftNeighborContext.UpdatePartition(locVerticalA, superblockInfo, subSize, subSize);
+ break;
+ case Av1PartitionType.VerticalB:
+ this.aboveNeighborContext.UpdatePartition(modeInfoLocation, tileLoc, subSize, subSize);
+ this.leftNeighborContext.UpdatePartition(modeInfoLocation, superblockInfo, subSize, subSize);
+ Point locVerticalB = new(modeInfoLocation.X, modeInfoLocation.Y + hbs);
+ this.aboveNeighborContext.UpdatePartition(locVerticalB, tileLoc, blockSize2, subSize);
+ this.leftNeighborContext.UpdatePartition(locVerticalB, superblockInfo, blockSize2, subSize);
+ break;
+ default:
+ throw new InvalidImageContentException($"Unknown partition type: {partition}");
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs
new file mode 100644
index 0000000000..6256867e7b
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1TransformBlockContext
+{
+ public int DcSignContext { get; set; }
+
+ public int SkipContext { get; set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs
new file mode 100644
index 0000000000..3f79f90244
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+///
+/// Information of a single Transform Block.
+///
+internal class Av1TransformInfo
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Av1TransformInfo(Av1TransformSize size, int offsetX, int offsetY)
+ {
+ this.Size = size;
+ this.OffsetX = offsetX;
+ this.OffsetY = offsetY;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to copy the information from.
+ public Av1TransformInfo(Av1TransformInfo originalInfo)
+ {
+ this.Size = originalInfo.Size;
+ this.OffsetX = originalInfo.OffsetX;
+ this.OffsetY = originalInfo.OffsetY;
+ }
+
+ ///
+ /// Gets or sets the transform size to be used for this Transform Block.
+ ///
+ public Av1TransformSize Size { get; internal set; }
+
+ ///
+ /// Gets or sets the transform type to be used for this Transform Block.
+ ///
+ public Av1TransformType Type { get; internal set; }
+
+ ///
+ /// Gets or sets the X offset of this block in ModeInfo units.
+ ///
+ public int OffsetX { get; internal set; }
+
+ ///
+ /// Gets or sets the Y offset of this block in ModeInfo units.
+ ///
+ public int OffsetY { get; internal set; }
+
+ ///
+ /// Gets or sets a value indicating whether the Code block flag is set.
+ ///
+ /// -
+ /// false
+ /// No residual for the block
+ ///
+ /// -
+ /// true
+ /// Residual exists for the block
+ ///
+ ///
+ ///
+ public bool CodeBlockFlag { get; internal set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs
new file mode 100644
index 0000000000..9e76b20f4f
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs
@@ -0,0 +1,284 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal class Av1BlockDecoder
+{
+ private readonly ObuSequenceHeader sequenceHeader;
+
+ private readonly ObuFrameHeader frameHeader;
+
+ private readonly Av1FrameInfo frameInfo;
+
+ private readonly Av1FrameBuffer frameBuffer;
+
+ private readonly bool isLoopFilterEnabled;
+
+ private readonly int[] currentCoefficientIndex;
+
+ public Av1BlockDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer)
+ {
+ this.sequenceHeader = sequenceHeader;
+ this.frameHeader = frameHeader;
+ this.frameInfo = frameInfo;
+ this.frameBuffer = frameBuffer;
+ int ySize = (1 << this.sequenceHeader.SuperblockSizeLog2) * (1 << this.sequenceHeader.SuperblockSizeLog2);
+ int inverseQuantizationSize = ySize +
+ (this.sequenceHeader.ColorConfig.SubSamplingX ? ySize >> 2 : ySize) +
+ (this.sequenceHeader.ColorConfig.SubSamplingY ? ySize >> 2 : ySize);
+ this.CurrentInverseQuantizationCoefficients = new int[inverseQuantizationSize];
+ this.isLoopFilterEnabled = false;
+ this.currentCoefficientIndex = new int[3];
+ }
+
+ public int[] CurrentInverseQuantizationCoefficients { get; private set; }
+
+ public void UpdateSuperblock(Av1SuperblockInfo superblockInfo)
+ {
+ this.currentCoefficientIndex[0] = 0;
+ this.currentCoefficientIndex[1] = 0;
+ this.currentCoefficientIndex[2] = 0;
+ }
+
+ ///
+ /// SVT: svt_aom_decode_block
+ ///
+ public void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
+ {
+ ObuColorConfig colorConfig = this.sequenceHeader.ColorConfig;
+ Av1TransformType transformType;
+ Av1TransformSize transformSize;
+ int transformUnitCount;
+ bool hasChroma = Av1TileReader.HasChroma(this.sequenceHeader, modeInfoPosition, blockSize);
+ Av1PartitionInfo partitionInfo = new(modeInfo, superblockInfo, hasChroma, Av1PartitionType.None);
+
+ int maxBlocksWide = partitionInfo.GetMaxBlockWide(blockSize, false);
+ int maxBlocksHigh = partitionInfo.GetMaxBlockHigh(blockSize, false);
+
+ bool isLossless = this.frameHeader.LosslessArray[modeInfo.SegmentId];
+ bool isLosslessBlock = isLossless && ((blockSize >= Av1BlockSize.Block64x64) && (blockSize <= Av1BlockSize.Block128x128));
+ int chromaTransformUnitCount = isLosslessBlock
+ ? (maxBlocksWide * maxBlocksHigh) >> ((colorConfig.SubSamplingX ? 1 : 0) + (colorConfig.SubSamplingY ? 1 : 0))
+ : modeInfo.TransformUnitsCount[(int)Av1Plane.U];
+ bool highBitDepth = false;
+ bool is16BitsPipeline = false;
+ int loopFilterStride = this.frameHeader.ModeInfoStride;
+ Av1PredictionDecoder predictionDecoder = new(this.sequenceHeader, this.frameHeader, false);
+ Av1InverseQuantizer inverseQuantizer = new(this.sequenceHeader, this.frameHeader);
+
+ for (int plane = 0; plane < colorConfig.PlaneCount; plane++)
+ {
+ int subX = (plane > 0) ? colorConfig.SubSamplingX ? 1 : 0 : 0;
+ int subY = (plane > 0) ? colorConfig.SubSamplingY ? 1 : 0 : 0;
+
+ if (plane != 0 && !partitionInfo.IsChroma)
+ {
+ continue;
+ }
+
+ int transformInfoIndex = plane switch
+ {
+ 2 => superblockInfo.TransformInfoIndexUv + modeInfo.FirstTransformLocation[plane - 1] + chromaTransformUnitCount,
+ 1 => superblockInfo.TransformInfoIndexY + modeInfo.FirstTransformLocation[plane],
+ 0 => superblockInfo.TransformInfoIndexY + modeInfo.FirstTransformLocation[plane],
+ _ => throw new InvalidImageContentException("Maximum of 3 color planes")
+ };
+ ref Av1TransformInfo transformInfo = ref Unsafe.Add(ref this.frameInfo.GetSuperblockTransform(plane, superblockInfo.Position), transformInfoIndex);
+
+ if (isLosslessBlock)
+ {
+ Guard.IsTrue(transformInfo.Size == Av1TransformSize.Size4x4, nameof(transformInfo.Size), "Lossless may only have 4x4 blocks.");
+ transformUnitCount = (maxBlocksWide * maxBlocksHigh) >> (subX + subY);
+ }
+ else
+ {
+ transformUnitCount = modeInfo.TransformUnitsCount[Math.Min(1, plane)];
+ }
+
+ Guard.IsFalse(transformUnitCount == 0, nameof(transformUnitCount), "Must have at least a single transform unit to decode.");
+
+ // SVT: svt_aom_derive_blk_pointers
+ DeriveBlockPointers(
+ this.frameBuffer,
+ plane,
+ (modeInfoPosition.X >> subX) << Av1Constants.ModeInfoSizeLog2,
+ (modeInfoPosition.Y >> subY) << Av1Constants.ModeInfoSizeLog2,
+ out Span blockReconstructionBuffer,
+ out int reconstructionStride,
+ subX,
+ subY);
+
+ for (int tu = 0; tu < transformUnitCount; tu++)
+ {
+ Span transformBlockReconstructionBuffer;
+ int transformBlockOffset;
+
+ transformSize = transformInfo.Size;
+ Span coefficients = superblockInfo.GetCoefficients((Av1Plane)plane)[this.currentCoefficientIndex[plane]..];
+
+ transformBlockOffset = ((transformInfo.OffsetY * reconstructionStride) + transformInfo.OffsetX) << Av1Constants.ModeInfoSizeLog2;
+ transformBlockReconstructionBuffer = blockReconstructionBuffer.Slice(transformBlockOffset << (highBitDepth ? 1 : 0));
+
+ if (this.isLoopFilterEnabled)
+ {
+ /*
+ if (plane != 2)
+ {
+ // SVT: svt_aom_fill_4x4_lf_param
+ Fill4x4LoopFilterParameters(
+ this.loopFilterContext,
+ (modeInfoPosition.X & (~subX)) + (transformInfo.OffsetX << subX),
+ (modeInfoPosition.Y & (~subY)) + (transformInfo.OffsetY << subY),
+ loopFilterStride,
+ transformSize,
+ subX,
+ subY,
+ plane);
+ }*/
+ }
+
+ // if (!inter_block)
+ if (true)
+ {
+ // SVT: svt_av1_predict_intra
+ predictionDecoder.Decode(
+ partitionInfo,
+ (Av1Plane)plane,
+ transformSize,
+ tileInfo,
+ transformBlockReconstructionBuffer,
+ reconstructionStride,
+ this.frameBuffer.BitDepth,
+ transformInfo.OffsetX,
+ transformInfo.OffsetY);
+ }
+
+ int numberOfCoefficients = 0;
+
+ if (!modeInfo.Skip && transformInfo.CodeBlockFlag)
+ {
+ Span quantizationCoefficients = this.CurrentInverseQuantizationCoefficients;
+ int inverseQuantizationSize = transformSize.GetWidth() * transformSize.GetHeight();
+ quantizationCoefficients[..inverseQuantizationSize].Clear();
+ transformType = transformInfo.Type;
+
+ // SVT: svt_aom_inverse_quantize
+ numberOfCoefficients = inverseQuantizer.InverseQuantize(
+ modeInfo, coefficients, quantizationCoefficients, transformType, transformSize, (Av1Plane)plane);
+ if (numberOfCoefficients != 0)
+ {
+ this.currentCoefficientIndex[plane] += numberOfCoefficients + 1;
+
+ if (this.frameBuffer.BitDepth == Av1BitDepth.EightBit && !is16BitsPipeline)
+ {
+ // SVT: svt_aom_inv_transform_recon8bit
+ Av1InverseTransformer.Reconstruct8Bit(
+ quantizationCoefficients,
+ transformBlockReconstructionBuffer,
+ reconstructionStride,
+ transformBlockReconstructionBuffer,
+ reconstructionStride,
+ transformSize,
+ transformType,
+ plane,
+ numberOfCoefficients,
+ isLossless);
+ }
+ else
+ {
+ throw new NotImplementedException("No support for 16 bit pipeline yet.");
+ }
+ }
+ }
+
+ // Store Luma for CFL if required!
+ if (plane == (int)Av1Plane.Y && StoreChromeFromLumeRequired(colorConfig, partitionInfo, hasChroma))
+ {
+ /*
+ // SVT: svt_cfl_store_tx
+ ChromaFromLumaStoreTransform(
+ partitionInfo,
+ this.chromaFromLumaContext,
+ transformInfo.OffsetY,
+ transformInfo.OffsetX,
+ transformSize,
+ blockSize,
+ colorConfig,
+ transformBlockReconstructionBuffer,
+ reconstructionStride,
+ is16BitsPipeline);
+ */
+ }
+
+ // increment transform pointer
+ transformInfo = ref Unsafe.Add(ref transformInfo, 1);
+ }
+ }
+ }
+
+ private static void DeriveBlockPointers(Av1FrameBuffer frameBuffer, int plane, int blockColumnInPixels, int blockRowInPixels, out Span blockReconstructionBuffer, out int reconstructionStride, int subX, int subY)
+ {
+ int blockOffset;
+
+ if (plane == 0)
+ {
+ blockOffset = ((frameBuffer.OriginY + blockRowInPixels) * frameBuffer.BufferY!.Width) +
+ (frameBuffer.OriginX + blockColumnInPixels);
+ reconstructionStride = frameBuffer.BufferY!.Width;
+ }
+ else if (plane == 1)
+ {
+ blockOffset = (((frameBuffer.OriginY >> subY) + blockRowInPixels) * frameBuffer.BufferCb!.Width) +
+ ((frameBuffer.OriginX >> subX) + blockColumnInPixels);
+ reconstructionStride = frameBuffer.BufferCb!.Width;
+ }
+ else
+ {
+ blockOffset = (((frameBuffer.OriginY >> subY) + blockRowInPixels) * frameBuffer.BufferCr!.Width) +
+ ((frameBuffer.OriginX >> subX) + blockColumnInPixels);
+ reconstructionStride = frameBuffer.BufferCr!.Width;
+ }
+
+ if (frameBuffer.BitDepth != Av1BitDepth.EightBit || frameBuffer.Is16BitPipeline)
+ {
+ // 16bit pipeline
+ blockOffset *= 2;
+ if (plane == 0)
+ {
+ blockReconstructionBuffer = frameBuffer.BufferY!.DangerousGetSingleSpan()[blockOffset..];
+ }
+ else if (plane == 1)
+ {
+ blockReconstructionBuffer = frameBuffer.BufferCb!.DangerousGetSingleSpan()[blockOffset..];
+ }
+ else
+ {
+ blockReconstructionBuffer = frameBuffer.BufferCr!.DangerousGetSingleSpan()[blockOffset..];
+ }
+ }
+ else
+ {
+ if (plane == 0)
+ {
+ blockReconstructionBuffer = frameBuffer.BufferY!.DangerousGetSingleSpan()[blockOffset..];
+ }
+ else if (plane == 1)
+ {
+ blockReconstructionBuffer = frameBuffer.BufferCb!.DangerousGetSingleSpan()[blockOffset..];
+ }
+ else
+ {
+ blockReconstructionBuffer = frameBuffer.BufferCr!.DangerousGetSingleSpan()[blockOffset..];
+ }
+ }
+ }
+
+ private static bool StoreChromeFromLumeRequired(ObuColorConfig colorConfig, Av1PartitionInfo partitionInfo, bool hasChroma) => false;
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1CoefficientShape.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1CoefficientShape.cs
new file mode 100644
index 0000000000..a4f9efc6e5
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1CoefficientShape.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal enum Av1CoefficientShape
+{
+ Default,
+ N2,
+ N4,
+ OnlyDc
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs
new file mode 100644
index 0000000000..0940425871
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs
@@ -0,0 +1,245 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal class Av1ForwardTransformer
+{
+ private const int NewSqrt = 5793;
+ private const int NewSqrtBitCount = 12;
+
+ private static readonly IAv1Forward1dTransformer?[] Transformers =
+ [
+ new Av1Dct4Forward1dTransformer(),
+ new Av1Dct8Forward1dTransformer(),
+ new Av1Dct16Forward1dTransformer(),
+ new Av1Dct32Forward1dTransformer(),
+ new Av1Dct64Forward1dTransformer(),
+ new Av1Adst4Forward1dTransformer(),
+ new Av1Adst8Forward1dTransformer(),
+ new Av1Adst16Forward1dTransformer(),
+ new Av1Adst32Forward1dTransformer(),
+ new Av1Identity4Forward1dTransformer(),
+ new Av1Identity8Forward1dTransformer(),
+ new Av1Identity16Forward1dTransformer(),
+ new Av1Identity32Forward1dTransformer(),
+ new Av1Identity64Forward1dTransformer(),
+ null
+ ];
+
+ private static readonly int[] TemporaryCoefficientsBuffer = new int[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize];
+
+ internal static void Transform2d(Span input, Span coefficients, uint stride, Av1TransformType transformType, Av1TransformSize transformSize, int bitDepth)
+ {
+ Av1Transform2dFlipConfiguration config = new(transformType, transformSize);
+ IAv1Forward1dTransformer? columnTransformer = GetTransformer(config.TransformFunctionTypeColumn);
+ IAv1Forward1dTransformer? rowTransformer = GetTransformer(config.TransformFunctionTypeRow);
+ Transform2d(columnTransformer, rowTransformer, input, coefficients, stride, config, bitDepth);
+ }
+
+ internal static void Transform2d(TColumn? transformFunctionColumn, TRow? transformFunctionRow, Span input, Span coefficients, uint stride, Av1Transform2dFlipConfiguration config, int bitDepth)
+ where TColumn : IAv1Forward1dTransformer
+ where TRow : IAv1Forward1dTransformer
+ {
+ if (transformFunctionColumn != null && transformFunctionRow != null)
+ {
+ Transform2dCore(transformFunctionColumn, transformFunctionRow, input, stride, coefficients, config, TemporaryCoefficientsBuffer, bitDepth);
+ }
+ else
+ {
+ throw new InvalidImageContentException($"Cannot find 1d transformer implementation for {config.TransformFunctionTypeColumn} or {config.TransformFunctionTypeRow}.");
+ }
+ }
+
+ private static IAv1Forward1dTransformer? GetTransformer(Av1TransformFunctionType transformerType)
+ => Transformers[(int)transformerType];
+
+ ///
+ /// SVT: av1_tranform_two_d_core_c
+ ///
+ private static void Transform2dCore(TColumn transformFunctionColumn, TRow transformFunctionRow, Span input, uint inputStride, Span output, Av1Transform2dFlipConfiguration config, Span buf, int bitDepth)
+ where TColumn : IAv1Forward1dTransformer
+ where TRow : IAv1Forward1dTransformer
+ {
+ int c, r;
+
+ // Note when assigning txfm_size_col, we use the txfm_size from the
+ // row configuration and vice versa. This is intentionally done to
+ // accurately perform rectangular transforms. When the transform is
+ // rectangular, the number of columns will be the same as the
+ // txfm_size stored in the row cfg struct. It will make no difference
+ // for square transforms.
+ int transformColumnCount = config.TransformSize.GetWidth();
+ int transformRowCount = config.TransformSize.GetHeight();
+ int transformCount = transformColumnCount * transformRowCount;
+
+ // Take the shift from the larger dimension in the rectangular case.
+ Span shift = config.Shift;
+ int rectangleType = GetRectangularRatio(transformColumnCount, transformRowCount);
+ Span stageRangeColumn = stackalloc byte[Av1Transform2dFlipConfiguration.MaxStageNumber];
+ Span stageRangeRow = stackalloc byte[Av1Transform2dFlipConfiguration.MaxStageNumber];
+
+ // assert(cfg->stage_num_col <= MAX_TXFM_STAGE_NUM);
+ // assert(cfg->stage_num_row <= MAX_TXFM_STAGE_NUM);
+ config.GenerateStageRange(bitDepth);
+
+ int cosBitColumn = config.CosBitColumn;
+ int cosBitRow = config.CosBitRow;
+
+ // ASSERT(txfm_func_col != NULL);
+ // ASSERT(txfm_func_row != NULL);
+ // use output buffer as temp buffer
+ Span tempInSpan = output[..transformRowCount];
+ Span tempOutSpan = output.Slice(transformRowCount, transformRowCount);
+ ref int tempIn = ref tempInSpan[0];
+ ref int tempOut = ref tempOutSpan[0];
+ ref short inputRef = ref input[0];
+ ref int outputRef = ref output[0];
+ ref int bufRef = ref buf[0];
+
+ // Columns
+ for (c = 0; c < transformColumnCount; ++c)
+ {
+ if (!config.FlipUpsideDown)
+ {
+ uint t = (uint)c;
+ for (r = 0; r < transformRowCount; ++r)
+ {
+ Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref inputRef, t);
+ t += inputStride;
+ }
+ }
+ else
+ {
+ uint t = (uint)(c + ((transformRowCount - 1) * (int)inputStride));
+ for (r = 0; r < transformRowCount; ++r)
+ {
+ // Flip upside down
+ Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref inputRef, t);
+ t -= inputStride;
+ }
+ }
+
+ RoundShiftArray(ref tempIn, transformRowCount, -shift[0]); // NM svt_av1_round_shift_array_c
+ transformFunctionColumn.Transform(tempInSpan, tempOutSpan, cosBitColumn, stageRangeColumn);
+ RoundShiftArray(ref tempOut, transformRowCount, -shift[1]); // NM svt_av1_round_shift_array_c
+ if (!config.FlipLeftToRight)
+ {
+ int t = c;
+ for (r = 0; r < transformRowCount; ++r)
+ {
+ Unsafe.Add(ref bufRef, t) = Unsafe.Add(ref tempOut, r);
+ t += transformColumnCount;
+ }
+ }
+ else
+ {
+ int t = transformColumnCount - c - 1;
+ for (r = 0; r < transformRowCount; ++r)
+ {
+ // flip from left to right
+ Unsafe.Add(ref bufRef, t) = Unsafe.Add(ref tempOut, r);
+ t += transformColumnCount;
+ }
+ }
+ }
+
+ // Rows
+ for (r = 0; r < transformCount; r += transformColumnCount)
+ {
+ transformFunctionRow.Transform(
+ buf.Slice(r, transformColumnCount),
+ output.Slice(r, transformColumnCount),
+ cosBitRow,
+ stageRangeRow);
+ RoundShiftArray(ref Unsafe.Add(ref outputRef, r), transformColumnCount, -shift[2]);
+
+ if (Math.Abs(rectangleType) == 1)
+ {
+ // Multiply everything by Sqrt2 if the transform is rectangular and the
+ // size difference is a factor of 2.
+ int t = r;
+ for (c = 0; c < transformColumnCount; ++c)
+ {
+ ref int current = ref Unsafe.Add(ref outputRef, t);
+ current = Av1Math.RoundShift((long)current * NewSqrt, NewSqrtBitCount);
+ t++;
+ }
+ }
+ }
+ }
+
+ private static void RoundShiftArray(ref int arr, int size, int bit)
+ {
+ if (bit == 0)
+ {
+ return;
+ }
+ else
+ {
+ nuint sz = (nuint)size;
+ if (bit > 0)
+ {
+ for (nuint i = 0; i < sz; i++)
+ {
+ ref int a = ref Unsafe.Add(ref arr, i);
+ a = Av1Math.RoundShift(a, bit);
+ }
+ }
+ else
+ {
+ for (nuint i = 0; i < sz; i++)
+ {
+ ref int a = ref Unsafe.Add(ref arr, i);
+ a *= 1 << (-bit);
+ }
+ }
+ }
+ }
+
+ ///
+ /// SVT: get_rect_tx_log_ratio
+ ///
+ public static int GetRectangularRatio(int col, int row)
+ {
+ if (col == row)
+ {
+ return 0;
+ }
+
+ if (col > row)
+ {
+ if (col == row * 2)
+ {
+ return 1;
+ }
+
+ if (col == row * 4)
+ {
+ return 2;
+ }
+
+ Guard.IsTrue(false, nameof(row), "Unsupported transform size");
+ }
+ else
+ {
+ if (row == col * 2)
+ {
+ return -1;
+ }
+
+ if (row == col * 4)
+ {
+ return -2;
+ }
+
+ Guard.IsTrue(false, nameof(row), "Unsupported transform size");
+ }
+
+ return 0; // Invalid
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformerFactory.cs
new file mode 100644
index 0000000000..c8664655d7
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformerFactory.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal static class Av1ForwardTransformerFactory
+{
+ internal static void EstimateTransform(
+ Span residualBuffer,
+ uint residualStride,
+ Span coefficientBuffer,
+ uint coefficientStride,
+ Av1TransformSize transformSize,
+ ref ulong threeQuadEnergy,
+ int bitDepth,
+ Av1TransformType transformType,
+ Av1PlaneType componentType,
+ Av1CoefficientShape transformCoefficientShape)
+ {
+ switch (transformCoefficientShape)
+ {
+ case Av1CoefficientShape.Default:
+ EstimateTransformDefault(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType);
+ break;
+ case Av1CoefficientShape.N2:
+ EstimateTransformN2(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType);
+ break;
+ case Av1CoefficientShape.N4:
+ EstimateTransformN4(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType);
+ break;
+ case Av1CoefficientShape.OnlyDc:
+ EstimateTransformOnlyDc(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType);
+ break;
+ }
+ }
+
+ private static void EstimateTransformDefault(
+ Span residualBuffer,
+ uint residualStride,
+ Span coefficientBuffer,
+ uint coefficientStride,
+ Av1TransformSize transformSize,
+ ref ulong threeQuadEnergy,
+ int bitDepth,
+ Av1TransformType transformType,
+ Av1PlaneType componentType)
+ => Av1ForwardTransformer.Transform2d(residualBuffer, coefficientBuffer, residualStride, transformType, transformSize, bitDepth);
+
+ private static void EstimateTransformN2(Span residualBuffer, uint residualStride, Span coefficientBuffer, uint coefficientStride, Av1TransformSize transformSize, ref ulong threeQuadEnergy, int bitDepth, Av1TransformType transformType, Av1PlaneType componentType) => throw new NotImplementedException();
+
+ private static void EstimateTransformN4(Span residualBuffer, uint residualStride, Span coefficientBuffer, uint coefficientStride, Av1TransformSize transformSize, ref ulong threeQuadEnergy, int bitDepth, Av1TransformType transformType, Av1PlaneType componentType) => throw new NotImplementedException();
+
+ private static void EstimateTransformOnlyDc(Span residualBuffer, uint residualStride, Span coefficientBuffer, uint coefficientStride, Av1TransformSize transformSize, ref ulong threeQuadEnergy, int bitDepth, Av1TransformType transformType, Av1PlaneType componentType) => throw new NotImplementedException();
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs
new file mode 100644
index 0000000000..97371b376b
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs
@@ -0,0 +1,363 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal class Av1Inverse2dTransformer
+{
+ private const int UnitQuantizationShift = 2;
+
+ ///
+ /// SVT: inv_txfm2d_add_c
+ ///
+ internal static void InverseTransform2dAdd(
+ Span input,
+ Span outputForRead,
+ int strideForRead,
+ Span outputForWrite,
+ int strideForWrite,
+ Av1Transform2dFlipConfiguration config,
+ Span transformFunctionBuffer,
+ int bitDepth)
+ {
+ // Note when assigning txfm_size_col, we use the txfm_size from the
+ // row configuration and vice versa. This is intentionally done to
+ // accurately perform rectangular transforms. When the transform is
+ // rectangular, the number of columns will be the same as the
+ // txfm_size stored in the row cfg struct. It will make no difference
+ // for square transforms.
+ int transformWidth = config.TransformSize.GetWidth();
+ int transformHeight = config.TransformSize.GetHeight();
+
+ // Take the shift from the larger dimension in the rectangular case.
+ Span shift = config.Shift;
+ int rectangleType = config.TransformSize.GetRectangleLogRatio();
+ config.GenerateStageRange(bitDepth);
+
+ int cosBitColumn = config.CosBitColumn;
+ int cosBitRow = config.CosBitRow;
+ IAv1Forward1dTransformer? functionColumn = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeColumn);
+ IAv1Forward1dTransformer? functionRow = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeRow);
+ Guard.NotNull(functionColumn);
+ Guard.NotNull(functionRow);
+
+ // txfm_buf's length is txfm_size_row * txfm_size_col + 2 * MAX(txfm_size_row, txfm_size_col)
+ // it is used for intermediate data buffering
+ int bufferOffset = Math.Max(transformHeight, transformWidth);
+ Guard.MustBeSizedAtLeast(transformFunctionBuffer, (transformHeight * transformWidth) + (2 * bufferOffset), nameof(transformFunctionBuffer));
+ Span tempIn = transformFunctionBuffer;
+ Span tempOut = tempIn.Slice(bufferOffset);
+ Span buf = tempOut.Slice(bufferOffset);
+ Span bufPtr = buf;
+ int c, r;
+
+ // Rows
+ for (r = 0; r < transformHeight; ++r)
+ {
+ if (Math.Abs(rectangleType) == 1)
+ {
+ for (c = 0; c < transformWidth; ++c)
+ {
+ tempIn[c] = Av1Math.RoundShift((long)input[c] * Av1InverseTransformMath.NewInverseSqrt2, Av1InverseTransformMath.NewSqrt2BitCount);
+ }
+
+ Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8));
+ functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow);
+ }
+ else
+ {
+ for (c = 0; c < transformWidth; ++c)
+ {
+ tempIn[c] = input[c];
+ }
+
+ Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8));
+ functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow);
+ }
+
+ Av1InverseTransformMath.RoundShiftArray(bufPtr, transformWidth, -shift[0]);
+ input.Slice(transformWidth);
+ bufPtr.Slice(transformWidth);
+ }
+
+ // Columns
+ for (c = 0; c < transformWidth; ++c)
+ {
+ if (!config.FlipLeftToRight)
+ {
+ for (r = 0; r < transformHeight; ++r)
+ {
+ tempIn[r] = buf[(r * transformWidth) + c];
+ }
+ }
+ else
+ {
+ // flip left right
+ for (r = 0; r < transformHeight; ++r)
+ {
+ tempIn[r] = buf[(r * transformWidth) + (transformWidth - c - 1)];
+ }
+ }
+
+ Av1InverseTransformMath.ClampBuffer(tempIn, transformHeight, (byte)Math.Max(bitDepth + 6, 16));
+ functionColumn.Transform(tempIn, tempOut, cosBitColumn, config.StageRangeColumn);
+ Av1InverseTransformMath.RoundShiftArray(tempOut, transformHeight, -shift[1]);
+ if (!config.FlipUpsideDown)
+ {
+ for (r = 0; r < transformHeight; ++r)
+ {
+ outputForWrite[(r * strideForWrite) + c] =
+ Av1InverseTransformMath.ClipPixelAdd(outputForRead[(r * strideForRead) + c], tempOut[r], bitDepth);
+ }
+ }
+ else
+ {
+ // flip upside down
+ for (r = 0; r < transformHeight; ++r)
+ {
+ outputForWrite[(r * strideForWrite) + c] = Av1InverseTransformMath.ClipPixelAdd(
+ outputForRead[(r * strideForRead) + c], tempOut[transformHeight - r - 1], bitDepth);
+ }
+ }
+ }
+ }
+
+ ///
+ /// SVT: inv_txfm2d_add_c
+ ///
+ internal static void InverseTransform2dAdd(
+ Span input,
+ Span outputForRead,
+ int strideForRead,
+ Span outputForWrite,
+ int strideForWrite,
+ Av1Transform2dFlipConfiguration config,
+ Span transformFunctionBuffer)
+ {
+ const int bitDepth = 8;
+
+ // Note when assigning txfm_size_col, we use the txfm_size from the
+ // row configuration and vice versa. This is intentionally done to
+ // accurately perform rectangular transforms. When the transform is
+ // rectangular, the number of columns will be the same as the
+ // txfm_size stored in the row cfg struct. It will make no difference
+ // for square transforms.
+ int transformWidth = config.TransformSize.GetWidth();
+ int transformHeight = config.TransformSize.GetHeight();
+
+ // Take the shift from the larger dimension in the rectangular case.
+ Span shift = config.Shift;
+ int rectangleType = config.TransformSize.GetRectangleLogRatio();
+ config.GenerateStageRange(bitDepth);
+
+ int cosBitColumn = config.CosBitColumn;
+ int cosBitRow = config.CosBitRow;
+ IAv1Forward1dTransformer? functionColumn = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeColumn);
+ IAv1Forward1dTransformer? functionRow = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeRow);
+ Guard.NotNull(functionColumn);
+ Guard.NotNull(functionRow);
+
+ // txfm_buf's length is txfm_size_row * txfm_size_col + 2 * MAX(txfm_size_row, txfm_size_col)
+ // it is used for intermediate data buffering
+ int bufferOffset = Math.Max(transformHeight, transformWidth);
+ Guard.MustBeSizedAtLeast(transformFunctionBuffer, (transformHeight * transformWidth) + (2 * bufferOffset), nameof(transformFunctionBuffer));
+ Span tempIn = transformFunctionBuffer;
+ Span tempOut = tempIn.Slice(bufferOffset);
+ Span buf = tempOut.Slice(bufferOffset);
+ Span bufPtr = buf;
+ int c, r;
+
+ // Rows
+ for (r = 0; r < transformHeight; ++r)
+ {
+ if (Math.Abs(rectangleType) == 1)
+ {
+ for (c = 0; c < transformWidth; ++c)
+ {
+ tempIn[c] = Av1Math.RoundShift((long)input[c] * Av1InverseTransformMath.NewInverseSqrt2, Av1InverseTransformMath.NewSqrt2BitCount);
+ }
+
+ Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8));
+ functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow);
+ }
+ else
+ {
+ for (c = 0; c < transformWidth; ++c)
+ {
+ tempIn[c] = input[c];
+ }
+
+ Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8));
+ functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow);
+ }
+
+ Av1InverseTransformMath.RoundShiftArray(bufPtr, transformWidth, -shift[0]);
+ input.Slice(transformWidth);
+ bufPtr.Slice(transformWidth);
+ }
+
+ // Columns
+ for (c = 0; c < transformWidth; ++c)
+ {
+ if (!config.FlipLeftToRight)
+ {
+ for (r = 0; r < transformHeight; ++r)
+ {
+ tempIn[r] = buf[(r * transformWidth) + c];
+ }
+ }
+ else
+ {
+ // flip left right
+ for (r = 0; r < transformHeight; ++r)
+ {
+ tempIn[r] = buf[(r * transformWidth) + (transformWidth - c - 1)];
+ }
+ }
+
+ Av1InverseTransformMath.ClampBuffer(tempIn, transformHeight, (byte)Math.Max(bitDepth + 6, 16));
+ functionColumn.Transform(tempIn, tempOut, cosBitColumn, config.StageRangeColumn);
+ Av1InverseTransformMath.RoundShiftArray(tempOut, transformHeight, -shift[1]);
+ if (!config.FlipUpsideDown)
+ {
+ for (r = 0; r < transformHeight; ++r)
+ {
+ outputForWrite[(r * strideForWrite) + c] =
+ Av1InverseTransformMath.ClipPixelAdd(outputForRead[(r * strideForRead) + c], tempOut[r]);
+ }
+ }
+ else
+ {
+ // flip upside down
+ for (r = 0; r < transformHeight; ++r)
+ {
+ outputForWrite[(r * strideForWrite) + c] = Av1InverseTransformMath.ClipPixelAdd(
+ outputForRead[(r * strideForRead) + c], tempOut[transformHeight - r - 1]);
+ }
+ }
+ }
+ }
+
+ ///
+ /// SVT: highbd_iwht4x4_add
+ ///
+ private static void InverseWhalshHadamard4x4(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int endOfBuffer, int bitDepth)
+ {
+ if (endOfBuffer > 1)
+ {
+ InverseWhalshHadamard4x4Add16(ref input, ref destinationForRead, strideForRead, ref destinationForWrite, strideForWrite, bitDepth);
+ }
+ else
+ {
+ InverseWhalshHadamard4x4Add1(ref input, ref destinationForRead, strideForRead, ref destinationForWrite, strideForWrite, bitDepth);
+ }
+ }
+
+ ///
+ /// SVT: svt_av1_highbd_iwht4x4_16_add_c
+ ///
+ private static void InverseWhalshHadamard4x4Add16(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int bitDepth)
+ {
+ /* 4-point reversible, orthonormal inverse Walsh-Hadamard in 3.5 adds,
+ 0.5 shifts per pixel. */
+ int i;
+ Span output = stackalloc ushort[16];
+ ushort a1, b1, c1, d1, e1;
+ ref int ip = ref input;
+ ref ushort op = ref output[0];
+ ref ushort opTmp = ref output[0];
+ ref ushort destForRead = ref Unsafe.As(ref destinationForRead);
+ ref ushort destForWrite = ref Unsafe.As(ref destinationForWrite);
+
+ for (i = 0; i < 4; i++)
+ {
+ a1 = (ushort)(ip >> UnitQuantizationShift);
+ c1 = (ushort)(Unsafe.Add(ref ip, 1) >> UnitQuantizationShift);
+ d1 = (ushort)(Unsafe.Add(ref ip, 2) >> UnitQuantizationShift);
+ b1 = (ushort)(Unsafe.Add(ref ip, 3) >> UnitQuantizationShift);
+ a1 += c1;
+ d1 -= b1;
+ e1 = (ushort)((a1 - d1) >> 1);
+ b1 = (ushort)(e1 - b1);
+ c1 = (ushort)(e1 - c1);
+ a1 -= b1;
+ d1 += c1;
+ op = a1;
+ Unsafe.Add(ref op, 1) = b1;
+ Unsafe.Add(ref op, 2) = c1;
+ Unsafe.Add(ref op, 3) = d1;
+ ip = ref Unsafe.Add(ref ip, 4);
+ op = ref Unsafe.Add(ref op, 4);
+ }
+
+ ip = opTmp;
+ for (i = 0; i < 4; i++)
+ {
+ a1 = (ushort)ip;
+ c1 = (ushort)Unsafe.Add(ref ip, 4);
+ d1 = (ushort)Unsafe.Add(ref ip, 8);
+ b1 = (ushort)Unsafe.Add(ref ip, 12);
+ a1 += c1;
+ d1 -= b1;
+ e1 = (ushort)((a1 - d1) >> 1);
+ b1 = (ushort)(e1 - b1);
+ c1 = (ushort)(e1 - c1);
+ a1 -= b1;
+ d1 += c1;
+ /* Disabled in normal build
+ range_check_value(a1, (int8_t)(bd + 1));
+ range_check_value(b1, (int8_t)(bd + 1));
+ range_check_value(c1, (int8_t)(bd + 1));
+ range_check_value(d1, (int8_t)(bd + 1));
+ */
+
+ destForWrite = Av1InverseTransformMath.ClipPixelAdd(destForRead, a1, bitDepth);
+ Unsafe.Add(ref destForWrite, strideForWrite) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), b1, bitDepth);
+ Unsafe.Add(ref destForWrite, strideForWrite * 2) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), c1, bitDepth);
+ Unsafe.Add(ref destForWrite, strideForWrite * 3) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 3), d1, bitDepth);
+
+ ip = ref Unsafe.Add(ref ip, 1);
+ destForRead = ref Unsafe.Add(ref destForRead, 1);
+ destForWrite = ref Unsafe.Add(ref destForWrite, 1);
+ }
+ }
+
+ ///
+ /// SVT: svt_av1_highbd_iwht4x4_1_add_c
+ ///
+ private static void InverseWhalshHadamard4x4Add1(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int bitDepth)
+ {
+ int i;
+ ushort a1, e1;
+ Span tmp = stackalloc int[4];
+ ref int ip = ref input;
+ ref int ipTmp = ref tmp[0];
+ ref int op = ref tmp[0];
+ ref ushort destForRead = ref Unsafe.As(ref destinationForRead);
+ ref ushort destForWrite = ref Unsafe.As(ref destinationForWrite);
+
+ a1 = (ushort)(ip >> UnitQuantizationShift);
+ e1 = (ushort)(a1 >> 1);
+ a1 -= e1;
+ op = a1;
+ Unsafe.Add(ref op, 1) = e1;
+ Unsafe.Add(ref op, 2) = e1;
+ Unsafe.Add(ref op, 3) = e1;
+
+ ip = ipTmp;
+ for (i = 0; i < 4; i++)
+ {
+ e1 = (ushort)(ip >> 1);
+ a1 = (ushort)(ip - e1);
+ destForWrite = Av1InverseTransformMath.ClipPixelAdd(destForRead, a1, bitDepth);
+ Unsafe.Add(ref destForWrite, strideForWrite) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), a1, bitDepth);
+ Unsafe.Add(ref destForWrite, strideForWrite * 2) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), a1, bitDepth);
+ Unsafe.Add(ref destForWrite, strideForWrite * 3) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 3), a1, bitDepth);
+ ip = ref Unsafe.Add(ref ip, 1);
+ destForRead = ref Unsafe.Add(ref destForRead, 1);
+ destForWrite = ref Unsafe.Add(ref destForWrite, 1);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs
new file mode 100644
index 0000000000..c8c7a04d3e
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs
@@ -0,0 +1,237 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal static class Av1InverseTransformMath
+{
+ public const int NewInverseSqrt2 = 2896;
+ public const int NewSqrt2BitCount = 12;
+
+ public static readonly int[,] AcQLookup = new int[3, 256]
+ {
+ {
+ 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
+ 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101,
+ 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138,
+ 140, 142, 144, 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 182, 185, 188,
+ 191, 194, 197, 200, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, 260,
+ 265, 270, 275, 280, 285, 290, 295, 300, 305, 311, 317, 323, 329, 335, 341, 347, 353, 359, 366,
+ 373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448, 456, 465, 474, 483, 492, 501, 510, 520,
+ 530, 540, 550, 560, 571, 582, 593, 604, 615, 627, 639, 651, 663, 676, 689, 702, 715, 729, 743,
+ 757, 771, 786, 801, 816, 832, 848, 864, 881, 898, 915, 933, 951, 969, 988, 1007, 1026, 1046, 1066,
+ 1087, 1108, 1129, 1151, 1173, 1196, 1219, 1243, 1267, 1292, 1317, 1343, 1369, 1396, 1423, 1451, 1479, 1508, 1537,
+ 1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828,
+ },
+ {
+ 4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63,
+ 67, 71, 75, 79, 83, 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145,
+ 149, 154, 158, 163, 168, 172, 177, 181, 186, 190, 195, 199, 204, 208, 213, 217, 222, 226, 231,
+ 235, 240, 244, 249, 253, 258, 262, 267, 271, 275, 280, 284, 289, 293, 297, 302, 306, 311, 315,
+ 319, 324, 328, 332, 337, 341, 345, 349, 354, 358, 362, 367, 371, 375, 379, 384, 388, 392, 396,
+ 401, 409, 417, 425, 433, 441, 449, 458, 466, 474, 482, 490, 498, 506, 514, 523, 531, 539, 547,
+ 555, 563, 571, 579, 588, 596, 604, 616, 628, 640, 652, 664, 676, 688, 700, 713, 725, 737, 749,
+ 761, 773, 785, 797, 809, 825, 841, 857, 873, 889, 905, 922, 938, 954, 970, 986, 1002, 1018, 1038,
+ 1058, 1078, 1098, 1118, 1138, 1158, 1178, 1198, 1218, 1242, 1266, 1290, 1314, 1338, 1362, 1386, 1411, 1435, 1463,
+ 1491, 1519, 1547, 1575, 1603, 1631, 1663, 1695, 1727, 1759, 1791, 1823, 1859, 1895, 1931, 1967, 2003, 2039, 2079,
+ 2119, 2159, 2199, 2239, 2283, 2327, 2371, 2415, 2459, 2507, 2555, 2603, 2651, 2703, 2755, 2807, 2859, 2915, 2971,
+ 3027, 3083, 3143, 3203, 3263, 3327, 3391, 3455, 3523, 3591, 3659, 3731, 3803, 3876, 3952, 4028, 4104, 4184, 4264,
+ 4348, 4432, 4516, 4604, 4692, 4784, 4876, 4972, 5068, 5168, 5268, 5372, 5476, 5584, 5692, 5804, 5916, 6032, 6148,
+ 6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172, 7312,
+ },
+ {
+ 4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168,
+ 183, 199, 214, 230, 247, 263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438,
+ 456, 475, 493, 511, 530, 548, 567, 586, 604, 623, 642, 660, 679, 698, 716, 735,
+ 753, 772, 791, 809, 828, 846, 865, 884, 902, 920, 939, 957, 976, 994, 1012, 1030,
+ 1049, 1067, 1085, 1103, 1121, 1139, 1157, 1175, 1193, 1211, 1229, 1246, 1264, 1282, 1299, 1317,
+ 1335, 1352, 1370, 1387, 1405, 1422, 1440, 1457, 1474, 1491, 1509, 1526, 1543, 1560, 1577, 1595,
+ 1627, 1660, 1693, 1725, 1758, 1791, 1824, 1856, 1889, 1922, 1954, 1987, 2020, 2052, 2085, 2118,
+ 2150, 2183, 2216, 2248, 2281, 2313, 2346, 2378, 2411, 2459, 2508, 2556, 2605, 2653, 2701, 2750,
+ 2798, 2847, 2895, 2943, 2992, 3040, 3088, 3137, 3185, 3234, 3298, 3362, 3426, 3491, 3555, 3619,
+ 3684, 3748, 3812, 3876, 3941, 4005, 4069, 4149, 4230, 4310, 4390, 4470, 4550, 4631, 4711, 4791,
+ 4871, 4967, 5064, 5160, 5256, 5352, 5448, 5544, 5641, 5737, 5849, 5961, 6073, 6185, 6297, 6410,
+ 6522, 6650, 6778, 6906, 7034, 7162, 7290, 7435, 7579, 7723, 7867, 8011, 8155, 8315, 8475, 8635,
+ 8795, 8956, 9132, 9308, 9484, 9660, 9836, 10028, 10220, 10412, 10604, 10812, 11020, 11228, 11437, 11661,
+ 11885, 12109, 12333, 12573, 12813, 13053, 13309, 13565, 13821, 14093, 14365, 14637, 14925, 15213, 15502, 15806,
+ 16110, 16414, 16734, 17054, 17390, 17726, 18062, 18414, 18766, 19134, 19502, 19886, 20270, 20670, 21070, 21486,
+ 21902, 22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143, 28687, 29247,
+ }
+ };
+
+ private static readonly int[,] DcQLookup = new int[3, 256]
+ {
+ {
+ 4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23,
+ 24, 25, 26, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40,
+ 41, 42, 43, 43, 44, 45, 46, 47, 48, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57,
+ 57, 58, 59, 60, 61, 62, 62, 63, 64, 65, 66, 66, 67, 68, 69, 70, 70, 71, 72, 73,
+ 74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 85, 87, 88, 90, 92,
+ 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117, 118, 120, 121,
+ 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 161, 164,
+ 166, 169, 172, 174, 177, 180, 182, 185, 187, 190, 192, 195, 199, 202, 205, 208, 211, 214, 217, 220,
+ 223, 226, 230, 233, 237, 240, 243, 247, 250, 253, 257, 261, 265, 269, 272, 276, 280, 284, 288, 292,
+ 296, 300, 304, 309, 313, 317, 322, 326, 330, 335, 340, 344, 349, 354, 359, 364, 369, 374, 379, 384,
+ 389, 395, 400, 406, 411, 417, 423, 429, 435, 441, 447, 454, 461, 467, 475, 482, 489, 497, 505, 513,
+ 522, 530, 539, 549, 559, 569, 579, 590, 602, 614, 626, 640, 654, 668, 684, 700, 717, 736, 755, 775,
+ 796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, 1184, 1232, 1282, 1336,
+ },
+ {
+ 4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57,
+ 60, 64, 68, 71, 75, 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128,
+ 132, 136, 140, 143, 147, 151, 155, 159, 163, 166, 170, 174, 178, 182, 185, 189, 193, 197, 200,
+ 204, 208, 212, 215, 219, 223, 226, 230, 233, 237, 241, 244, 248, 251, 255, 259, 262, 266, 269,
+ 273, 276, 280, 283, 287, 290, 293, 297, 300, 304, 307, 310, 314, 317, 321, 324, 327, 331, 334,
+ 337, 343, 350, 356, 362, 369, 375, 381, 387, 394, 400, 406, 412, 418, 424, 430, 436, 442, 448,
+ 454, 460, 466, 472, 478, 484, 490, 499, 507, 516, 525, 533, 542, 550, 559, 567, 576, 584, 592,
+ 601, 609, 617, 625, 634, 644, 655, 666, 676, 687, 698, 708, 718, 729, 739, 749, 759, 770, 782,
+ 795, 807, 819, 831, 844, 856, 868, 880, 891, 906, 920, 933, 947, 961, 975, 988, 1001, 1015, 1030,
+ 1045, 1061, 1076, 1090, 1105, 1120, 1137, 1153, 1170, 1186, 1202, 1218, 1236, 1253, 1271, 1288, 1306, 1323, 1342,
+ 1361, 1379, 1398, 1416, 1436, 1456, 1476, 1496, 1516, 1537, 1559, 1580, 1601, 1624, 1647, 1670, 1692, 1717, 1741,
+ 1766, 1791, 1817, 1844, 1871, 1900, 1929, 1958, 1990, 2021, 2054, 2088, 2123, 2159, 2197, 2236, 2276, 2319, 2363,
+ 2410, 2458, 2508, 2561, 2616, 2675, 2737, 2802, 2871, 2944, 3020, 3102, 3188, 3280, 3375, 3478, 3586, 3702, 3823,
+ 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347,
+ },
+ {
+ 4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153,
+ 166, 180, 194, 208, 222, 237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390,
+ 405, 421, 437, 453, 469, 484, 500, 516, 532, 548, 564, 580, 596, 611, 627, 643,
+ 659, 674, 690, 706, 721, 737, 752, 768, 783, 798, 814, 829, 844, 859, 874, 889,
+ 904, 919, 934, 949, 964, 978, 993, 1008, 1022, 1037, 1051, 1065, 1080, 1094, 1108, 1122,
+ 1136, 1151, 1165, 1179, 1192, 1206, 1220, 1234, 1248, 1261, 1275, 1288, 1302, 1315, 1329, 1342,
+ 1368, 1393, 1419, 1444, 1469, 1494, 1519, 1544, 1569, 1594, 1618, 1643, 1668, 1692, 1717, 1741,
+ 1765, 1789, 1814, 1838, 1862, 1885, 1909, 1933, 1957, 1992, 2027, 2061, 2096, 2130, 2165, 2199,
+ 2233, 2267, 2300, 2334, 2367, 2400, 2434, 2467, 2499, 2532, 2575, 2618, 2661, 2704, 2746, 2788,
+ 2830, 2872, 2913, 2954, 2995, 3036, 3076, 3127, 3177, 3226, 3275, 3324, 3373, 3421, 3469, 3517,
+ 3565, 3621, 3677, 3733, 3788, 3843, 3897, 3951, 4005, 4058, 4119, 4181, 4241, 4301, 4361, 4420,
+ 4479, 4546, 4612, 4677, 4742, 4807, 4871, 4942, 5013, 5083, 5153, 5222, 5291, 5367, 5442, 5517,
+ 5591, 5665, 5745, 5825, 5905, 5984, 6063, 6149, 6234, 6319, 6404, 6495, 6587, 6678, 6769, 6867,
+ 6966, 7064, 7163, 7269, 7376, 7483, 7599, 7715, 7832, 7958, 8085, 8214, 8352, 8492, 8635, 8788,
+ 8945, 9104, 9275, 9450, 9639, 9832, 10031, 10245, 10465, 10702, 10946, 11210, 11482, 11776, 12081, 12409,
+ 12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943, 17575, 18237, 18949, 19718, 20521, 21387,
+ }
+ };
+
+ public static int GetDcQuantization(int qIndex, int delta, Av1BitDepth bitDepth)
+ => DcQLookup[(int)bitDepth, Av1Math.Clip3(0, 255, qIndex + delta)];
+
+ public static int GetAcQuantization(int qIndex, int delta, Av1BitDepth bitDepth)
+ => AcQLookup[(int)bitDepth, Av1Math.Clip3(0, 255, qIndex + delta)];
+
+ public static int GetQzbinFactor(int q, Av1BitDepth bitDepth)
+ {
+ int quant = GetDcQuantization(q, 0, bitDepth);
+
+ // Bit hack to get to:
+ // EightBit => 148
+ // TenBit => 592
+ // TwelveBit => 2368
+ int shift = (int)bitDepth << 1;
+ int threshold = (1 << shift) * 148;
+ return q == 0 ? 64 : (quant < threshold ? 84 : 80);
+ }
+
+ public static void InvertQuantization(out int quantization, out int shift, int d)
+ {
+ uint t;
+ int l, m;
+ t = (uint)d;
+ for (l = 0; t > 1; l++)
+ {
+ t >>= 1;
+ }
+
+ m = 1 + ((1 << (16 + l)) / d);
+ quantization = m - (1 << 16);
+ shift = 1 << (16 - l);
+ }
+
+ public static byte ClipPixelAdd(byte dest, long trans)
+ {
+ trans = CheckRange(trans, 8);
+ return (byte)ClipPixelHighBitDepth(dest + trans, 8);
+ }
+
+ public static ushort ClipPixelAdd(ushort dest, long trans, int bitDepth)
+ {
+ trans = CheckRange(trans, bitDepth);
+ return ClipPixelHighBitDepth(dest + trans, bitDepth);
+ }
+
+ private static ushort ClipPixelHighBitDepth(long val, int bd)
+ {
+ switch (bd)
+ {
+ case 8:
+ default:
+ return (ushort)Av1Math.Clamp(val, 0, 255);
+ case 10:
+ return (ushort)Av1Math.Clamp(val, 0, 1023);
+ case 12:
+ return (ushort)Av1Math.Clamp(val, 0, 4095);
+ }
+ }
+
+ public static void RoundShiftArray(Span arr, int size, int bit)
+ {
+ int i;
+ if (bit == 0)
+ {
+ return;
+ }
+ else
+ {
+ if (bit > 0)
+ {
+ for (i = 0; i < size; i++)
+ {
+ arr[i] = Av1Math.RoundShift(arr[i], bit);
+ }
+ }
+ else
+ {
+ for (i = 0; i < size; i++)
+ {
+ arr[i] = arr[i] * (1 << (-bit));
+ }
+ }
+ }
+ }
+
+ internal static void ClampBuffer(Span buffer, int size, byte bit)
+ {
+ for (int i = 0; i < size; i++)
+ {
+ buffer[i] = ClampValue(buffer[i], bit);
+ }
+ }
+
+ private static int ClampValue(int value, byte bit)
+ {
+ if (bit <= 0)
+ {
+ return value; // Do nothing for invalid clamp bit.
+ }
+
+ long max_value = (1L << (bit - 1)) - 1;
+ long min_value = -(1L << (bit - 1));
+ return (int)Av1Math.Clamp(value, min_value, max_value);
+ }
+
+ private static long CheckRange(long input, int bd)
+ {
+ // AV1 TX case
+ // - 8 bit: signed 16 bit integer
+ // - 10 bit: signed 18 bit integer
+ // - 12 bit: signed 20 bit integer
+ // - max quantization error = 1828 << (bd - 8)
+ int int_max = (1 << (7 + bd)) - 1 + (914 << (bd - 7));
+ int int_min = -int_max - 1;
+ return Av1Math.Clamp(input, int_min, int_max);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs
new file mode 100644
index 0000000000..ed05dc9e6d
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal class Av1InverseTransformer
+{
+ ///
+ /// SVT: svt_aom_inv_transform_recon8bit
+ ///
+ public static void Reconstruct8Bit(Span coefficientsBuffer, Span reconstructionBufferRead, int reconstructionReadStride, Span reconstructionBufferWrite, int reconstructionWriteStride, Av1TransformSize transformSize, Av1TransformType transformType, int plane, int numberOfCoefficients, bool isLossless)
+ {
+ Av1TransformFunctionParameters transformFunctionParameters = new()
+ {
+ TransformType = transformType,
+ TransformSize = transformSize,
+ EndOfBuffer = numberOfCoefficients,
+ IsLossless = isLossless,
+ BitDepth = 8,
+ Is16BitPipeline = false
+ };
+
+ if (reconstructionBufferRead != reconstructionBufferWrite)
+ {
+ /* When output pointers to read and write are differents,
+ * then kernel copy also all buffer from read to write,
+ * and cannot be limited by End Of Buffer calculations. */
+ transformFunctionParameters.EndOfBuffer = GetMaxEndOfBuffer(transformSize);
+ }
+
+ Av1InverseTransformerFactory.InverseTransformAdd(
+ coefficientsBuffer, reconstructionBufferRead, reconstructionReadStride, reconstructionBufferWrite, reconstructionWriteStride, transformFunctionParameters);
+ }
+
+ ///
+ /// SVT: av1_get_max_eob
+ ///
+ private static int GetMaxEndOfBuffer(Av1TransformSize transformSize)
+ {
+ if (transformSize is Av1TransformSize.Size64x64 or Av1TransformSize.Size64x32 or Av1TransformSize.Size32x64)
+ {
+ return 1024;
+ }
+
+ if (transformSize is Av1TransformSize.Size16x64 or Av1TransformSize.Size64x16)
+ {
+ return 512;
+ }
+
+ return transformSize.GetSize2d();
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs
new file mode 100644
index 0000000000..1f73edae1b
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal static class Av1InverseTransformerFactory
+{
+ public static unsafe void InverseTransformAdd(Span coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters)
+ {
+ Guard.MustBeLessThanOrEqualTo(transformFunctionParameters.BitDepth, 8, nameof(transformFunctionParameters));
+ Guard.IsFalse(transformFunctionParameters.Is16BitPipeline, nameof(transformFunctionParameters), "Calling 8-bit pipeline while 16-bit is requested.");
+ int width = transformFunctionParameters.TransformSize.GetWidth();
+ int height = transformFunctionParameters.TransformSize.GetHeight();
+ Span buffer = new int[(width * height) + (2 * Math.Max(width, height))];
+ Av1Transform2dFlipConfiguration config = new(transformFunctionParameters.TransformType, transformFunctionParameters.TransformSize);
+ Av1Inverse2dTransformer.InverseTransform2dAdd(coefficients, readBuffer, readStride, writeBuffer, writeStride, config, buffer);
+ }
+
+ public static unsafe void InverseTransformAdd(Span coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters)
+ {
+ Guard.IsTrue(transformFunctionParameters.Is16BitPipeline, nameof(transformFunctionParameters), "Calling 16-bit pipeline while 8-bit is requested.");
+ int width = transformFunctionParameters.TransformSize.GetWidth();
+ int height = transformFunctionParameters.TransformSize.GetHeight();
+ Span buffer = new int[(width * height) + (2 * Math.Max(width, height))];
+ Av1Transform2dFlipConfiguration config = new(transformFunctionParameters.TransformType, transformFunctionParameters.TransformSize);
+ Av1Inverse2dTransformer.InverseTransform2dAdd(coefficients, readBuffer, readStride, writeBuffer, writeStride, config, buffer, transformFunctionParameters.BitDepth);
+ }
+
+ internal static IAv1Forward1dTransformer? GetTransformer(Av1TransformFunctionType type) => type switch
+ {
+ Av1TransformFunctionType.Dct4 => new Av1Dct4Inverse1dTransformer(),
+ Av1TransformFunctionType.Adst4 => new Av1Adst4Inverse1dTransformer(),
+ Av1TransformFunctionType.Identity4 => new Av1Identity4Inverse1dTransformer(),
+ _ => null
+ };
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs
new file mode 100644
index 0000000000..94650b1766
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal readonly struct Av1ScanOrder
+{
+ public Av1ScanOrder(short[] scan)
+ {
+ this.Scan = scan;
+ this.InverseScan = [];
+ this.Neighbors = [];
+ }
+
+ public Av1ScanOrder(short[] scan, short[] inverseScan, short[] neighbors)
+ {
+ this.Scan = scan;
+ this.InverseScan = inverseScan;
+ this.Neighbors = neighbors;
+ }
+
+ public short[] Scan { get; }
+
+ public short[] InverseScan { get; }
+
+ public short[] Neighbors { get; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs
new file mode 100644
index 0000000000..5acac18c7f
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs
@@ -0,0 +1,851 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal static class Av1ScanOrderConstants
+{
+ public const int QuantizationMatrixLevelBitCount = 4;
+ public const int QuantizationMatrixLevelCount = 1 << QuantizationMatrixLevelBitCount;
+
+ private static readonly short[] DefaultScan4x4 = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
+ private static readonly short[] DefaultScan8x8 = [
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48,
+ 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23,
+ 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63];
+
+ private static readonly short[] DefaultScan16x16 = [
+ 0, 1, 16, 32, 17, 2, 3, 18, 33, 48, 64, 49, 34, 19, 4, 5, 20, 35, 50, 65, 80, 96,
+ 81, 66, 51, 36, 21, 6, 7, 22, 37, 52, 67, 82, 97, 112, 128, 113, 98, 83, 68, 53, 38, 23,
+ 8, 9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10,
+ 11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57,
+ 42, 27, 12, 13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193, 208, 224, 209, 194, 179, 164,
+ 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180,
+ 195, 210, 225, 240, 241, 226, 211, 196, 181, 166, 151, 136, 121, 106, 91, 76, 61, 46, 31, 47, 62, 77,
+ 92, 107, 122, 137, 152, 167, 182, 197, 212, 227, 242, 243, 228, 213, 198, 183, 168, 153, 138, 123, 108, 93,
+ 78, 63, 79, 94, 109, 124, 139, 154, 169, 184, 199, 214, 229, 244, 245, 230, 215, 200, 185, 170, 155, 140,
+ 125, 110, 95, 111, 126, 141, 156, 171, 186, 201, 216, 231, 246, 247, 232, 217, 202, 187, 172, 157, 142, 127,
+ 143, 158, 173, 188, 203, 218, 233, 248, 249, 234, 219, 204, 189, 174, 159, 175, 190, 205, 220, 235, 250, 251,
+ 236, 221, 206, 191, 207, 222, 237, 252, 253, 238, 223, 239, 254, 255];
+
+ private static readonly short[] DefaultScan32x32 = [
+ 0, 1, 32, 64, 33, 2, 3, 34, 65, 96, 128, 97, 66, 35, 4, 5, 36, 67, 98, 129, 160,
+ 192, 161, 130, 99, 68, 37, 6, 7, 38, 69, 100, 131, 162, 193, 224, 256, 225, 194, 163, 132, 101,
+ 70, 39, 8, 9, 40, 71, 102, 133, 164, 195, 226, 257, 288, 320, 289, 258, 227, 196, 165, 134, 103,
+ 72, 41, 10, 11, 42, 73, 104, 135, 166, 197, 228, 259, 290, 321, 352, 384, 353, 322, 291, 260, 229,
+ 198, 167, 136, 105, 74, 43, 12, 13, 44, 75, 106, 137, 168, 199, 230, 261, 292, 323, 354, 385, 416,
+ 448, 417, 386, 355, 324, 293, 262, 231, 200, 169, 138, 107, 76, 45, 14, 15, 46, 77, 108, 139, 170,
+ 201, 232, 263, 294, 325, 356, 387, 418, 449, 480, 512, 481, 450, 419, 388, 357, 326, 295, 264, 233, 202,
+ 171, 140, 109, 78, 47, 16, 17, 48, 79, 110, 141, 172, 203, 234, 265, 296, 327, 358, 389, 420, 451,
+ 482, 513, 544, 576, 545, 514, 483, 452, 421, 390, 359, 328, 297, 266, 235, 204, 173, 142, 111, 80, 49,
+ 18, 19, 50, 81, 112, 143, 174, 205, 236, 267, 298, 329, 360, 391, 422, 453, 484, 515, 546, 577, 608,
+ 640, 609, 578, 547, 516, 485, 454, 423, 392, 361, 330, 299, 268, 237, 206, 175, 144, 113, 82, 51, 20,
+ 21, 52, 83, 114, 145, 176, 207, 238, 269, 300, 331, 362, 393, 424, 455, 486, 517, 548, 579, 610, 641,
+ 672, 704, 673, 642, 611, 580, 549, 518, 487, 456, 425, 394, 363, 332, 301, 270, 239, 208, 177, 146, 115,
+ 84, 53, 22, 23, 54, 85, 116, 147, 178, 209, 240, 271, 302, 333, 364, 395, 426, 457, 488, 519, 550,
+ 581, 612, 643, 674, 705, 736, 768, 737, 706, 675, 644, 613, 582, 551, 520, 489, 458, 427, 396, 365, 334,
+ 303, 272, 241, 210, 179, 148, 117, 86, 55, 24, 25, 56, 87, 118, 149, 180, 211, 242, 273, 304, 335,
+ 366, 397, 428, 459, 490, 521, 552, 583, 614, 645, 676, 707, 738, 769, 800, 832, 801, 770, 739, 708, 677,
+ 646, 615, 584, 553, 522, 491, 460, 429, 398, 367, 336, 305, 274, 243, 212, 181, 150, 119, 88, 57, 26,
+ 27, 58, 89, 120, 151, 182, 213, 244, 275, 306, 337, 368, 399, 430, 461, 492, 523, 554, 585, 616, 647,
+ 678, 709, 740, 771, 802, 833, 864, 896, 865, 834, 803, 772, 741, 710, 679, 648, 617, 586, 555, 524, 493,
+ 462, 431, 400, 369, 338, 307, 276, 245, 214, 183, 152, 121, 90, 59, 28, 29, 60, 91, 122, 153, 184,
+ 215, 246, 277, 308, 339, 370, 401, 432, 463, 494, 525, 556, 587, 618, 649, 680, 711, 742, 773, 804, 835,
+ 866, 897, 928, 960, 929, 898, 867, 836, 805, 774, 743, 712, 681, 650, 619, 588, 557, 526, 495, 464, 433,
+ 402, 371, 340, 309, 278, 247, 216, 185, 154, 123, 92, 61, 30, 31, 62, 93, 124, 155, 186, 217, 248,
+ 279, 310, 341, 372, 403, 434, 465, 496, 527, 558, 589, 620, 651, 682, 713, 744, 775, 806, 837, 868, 899,
+ 930, 961, 992, 993, 962, 931, 900, 869, 838, 807, 776, 745, 714, 683, 652, 621, 590, 559, 528, 497, 466,
+ 435, 404, 373, 342, 311, 280, 249, 218, 187, 156, 125, 94, 63, 95, 126, 157, 188, 219, 250, 281, 312,
+ 343, 374, 405, 436, 467, 498, 529, 560, 591, 622, 653, 684, 715, 746, 777, 808, 839, 870, 901, 932, 963,
+ 994, 995, 964, 933, 902, 871, 840, 809, 778, 747, 716, 685, 654, 623, 592, 561, 530, 499, 468, 437, 406,
+ 375, 344, 313, 282, 251, 220, 189, 158, 127, 159, 190, 221, 252, 283, 314, 345, 376, 407, 438, 469, 500,
+ 531, 562, 593, 624, 655, 686, 717, 748, 779, 810, 841, 872, 903, 934, 965, 996, 997, 966, 935, 904, 873,
+ 842, 811, 780, 749, 718, 687, 656, 625, 594, 563, 532, 501, 470, 439, 408, 377, 346, 315, 284, 253, 222,
+ 191, 223, 254, 285, 316, 347, 378, 409, 440, 471, 502, 533, 564, 595, 626, 657, 688, 719, 750, 781, 812,
+ 843, 874, 905, 936, 967, 998, 999, 968, 937, 906, 875, 844, 813, 782, 751, 720, 689, 658, 627, 596, 565,
+ 534, 503, 472, 441, 410, 379, 348, 317, 286, 255, 287, 318, 349, 380, 411, 442, 473, 504, 535, 566, 597,
+ 628, 659, 690, 721, 752, 783, 814, 845, 876, 907, 938, 969, 1000, 1001, 970, 939, 908, 877, 846, 815, 784,
+ 753, 722, 691, 660, 629, 598, 567, 536, 505, 474, 443, 412, 381, 350, 319, 351, 382, 413, 444, 475, 506,
+ 537, 568, 599, 630, 661, 692, 723, 754, 785, 816, 847, 878, 909, 940, 971, 1002, 1003, 972, 941, 910, 879,
+ 848, 817, 786, 755, 724, 693, 662, 631, 600, 569, 538, 507, 476, 445, 414, 383, 415, 446, 477, 508, 539,
+ 570, 601, 632, 663, 694, 725, 756, 787, 818, 849, 880, 911, 942, 973, 1004, 1005, 974, 943, 912, 881, 850,
+ 819, 788, 757, 726, 695, 664, 633, 602, 571, 540, 509, 478, 447, 479, 510, 541, 572, 603, 634, 665, 696,
+ 727, 758, 789, 820, 851, 882, 913, 944, 975, 1006, 1007, 976, 945, 914, 883, 852, 821, 790, 759, 728, 697,
+ 666, 635, 604, 573, 542, 511, 543, 574, 605, 636, 667, 698, 729, 760, 791, 822, 853, 884, 915, 946, 977,
+ 1008, 1009, 978, 947, 916, 885, 854, 823, 792, 761, 730, 699, 668, 637, 606, 575, 607, 638, 669, 700, 731,
+ 762, 793, 824, 855, 886, 917, 948, 979, 1010, 1011, 980, 949, 918, 887, 856, 825, 794, 763, 732, 701, 670,
+ 639, 671, 702, 733, 764, 795, 826, 857, 888, 919, 950, 981, 1012, 1013, 982, 951, 920, 889, 858, 827, 796,
+ 765, 734, 703, 735, 766, 797, 828, 859, 890, 921, 952, 983, 1014, 1015, 984, 953, 922, 891, 860, 829, 798,
+ 767, 799, 830, 861, 892, 923, 954, 985, 1016, 1017, 986, 955, 924, 893, 862, 831, 863, 894, 925, 956, 987,
+ 1018, 1019, 988, 957, 926, 895, 927, 958, 989, 1020, 1021, 990, 959, 991, 1022, 1023];
+
+ private static readonly short[] DefaultScan4x8 = [
+ 0, 1, 4, 2, 5, 8, 3, 6, 9, 12, 7, 10, 13, 16, 11, 14,
+ 17, 20, 15, 18, 21, 24, 19, 22, 25, 28, 23, 26, 29, 27, 30, 31,];
+
+ private static readonly short[] DefaultScan8x4 = [
+ 0, 8, 1, 16, 9, 2, 24, 17, 10, 3, 25, 18, 11, 4, 26, 19,
+ 12, 5, 27, 20, 13, 6, 28, 21, 14, 7, 29, 22, 15, 30, 23, 31,];
+
+ private static readonly short[] DefaultScan8x16 = [
+ 0, 1, 8, 2, 9, 16, 3, 10, 17, 24, 4, 11, 18, 25, 32, 5, 12, 19, 26, 33, 40, 6,
+ 13, 20, 27, 34, 41, 48, 7, 14, 21, 28, 35, 42, 49, 56, 15, 22, 29, 36, 43, 50, 57, 64,
+ 23, 30, 37, 44, 51, 58, 65, 72, 31, 38, 45, 52, 59, 66, 73, 80, 39, 46, 53, 60, 67, 74,
+ 81, 88, 47, 54, 61, 68, 75, 82, 89, 96, 55, 62, 69, 76, 83, 90, 97, 104, 63, 70, 77, 84,
+ 91, 98, 105, 112, 71, 78, 85, 92, 99, 106, 113, 120, 79, 86, 93, 100, 107, 114, 121, 87, 94, 101,
+ 108, 115, 122, 95, 102, 109, 116, 123, 103, 110, 117, 124, 111, 118, 125, 119, 126, 127,];
+
+ private static readonly short[] DefaultScan16x8 = [
+ 0, 16, 1, 32, 17, 2, 48, 33, 18, 3, 64, 49, 34, 19, 4, 80, 65, 50, 35, 20, 5, 96,
+ 81, 66, 51, 36, 21, 6, 112, 97, 82, 67, 52, 37, 22, 7, 113, 98, 83, 68, 53, 38, 23, 8,
+ 114, 99, 84, 69, 54, 39, 24, 9, 115, 100, 85, 70, 55, 40, 25, 10, 116, 101, 86, 71, 56, 41,
+ 26, 11, 117, 102, 87, 72, 57, 42, 27, 12, 118, 103, 88, 73, 58, 43, 28, 13, 119, 104, 89, 74,
+ 59, 44, 29, 14, 120, 105, 90, 75, 60, 45, 30, 15, 121, 106, 91, 76, 61, 46, 31, 122, 107, 92,
+ 77, 62, 47, 123, 108, 93, 78, 63, 124, 109, 94, 79, 125, 110, 95, 126, 111, 127,];
+
+ private static readonly short[] DefaultScan16x32 = [
+ 0, 1, 16, 2, 17, 32, 3, 18, 33, 48, 4, 19, 34, 49, 64, 5, 20, 35, 50, 65, 80, 6, 21,
+ 36, 51, 66, 81, 96, 7, 22, 37, 52, 67, 82, 97, 112, 8, 23, 38, 53, 68, 83, 98, 113, 128, 9,
+ 24, 39, 54, 69, 84, 99, 114, 129, 144, 10, 25, 40, 55, 70, 85, 100, 115, 130, 145, 160, 11, 26, 41,
+ 56, 71, 86, 101, 116, 131, 146, 161, 176, 12, 27, 42, 57, 72, 87, 102, 117, 132, 147, 162, 177, 192, 13,
+ 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193, 208, 14, 29, 44, 59, 74, 89, 104, 119, 134, 149,
+ 164, 179, 194, 209, 224, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 31, 46,
+ 61, 76, 91, 106, 121, 136, 151, 166, 181, 196, 211, 226, 241, 256, 47, 62, 77, 92, 107, 122, 137, 152, 167,
+ 182, 197, 212, 227, 242, 257, 272, 63, 78, 93, 108, 123, 138, 153, 168, 183, 198, 213, 228, 243, 258, 273, 288,
+ 79, 94, 109, 124, 139, 154, 169, 184, 199, 214, 229, 244, 259, 274, 289, 304, 95, 110, 125, 140, 155, 170, 185,
+ 200, 215, 230, 245, 260, 275, 290, 305, 320, 111, 126, 141, 156, 171, 186, 201, 216, 231, 246, 261, 276, 291, 306,
+ 321, 336, 127, 142, 157, 172, 187, 202, 217, 232, 247, 262, 277, 292, 307, 322, 337, 352, 143, 158, 173, 188, 203,
+ 218, 233, 248, 263, 278, 293, 308, 323, 338, 353, 368, 159, 174, 189, 204, 219, 234, 249, 264, 279, 294, 309, 324,
+ 339, 354, 369, 384, 175, 190, 205, 220, 235, 250, 265, 280, 295, 310, 325, 340, 355, 370, 385, 400, 191, 206, 221,
+ 236, 251, 266, 281, 296, 311, 326, 341, 356, 371, 386, 401, 416, 207, 222, 237, 252, 267, 282, 297, 312, 327, 342,
+ 357, 372, 387, 402, 417, 432, 223, 238, 253, 268, 283, 298, 313, 328, 343, 358, 373, 388, 403, 418, 433, 448, 239,
+ 254, 269, 284, 299, 314, 329, 344, 359, 374, 389, 404, 419, 434, 449, 464, 255, 270, 285, 300, 315, 330, 345, 360,
+ 375, 390, 405, 420, 435, 450, 465, 480, 271, 286, 301, 316, 331, 346, 361, 376, 391, 406, 421, 436, 451, 466, 481,
+ 496, 287, 302, 317, 332, 347, 362, 377, 392, 407, 422, 437, 452, 467, 482, 497, 303, 318, 333, 348, 363, 378, 393,
+ 408, 423, 438, 453, 468, 483, 498, 319, 334, 349, 364, 379, 394, 409, 424, 439, 454, 469, 484, 499, 335, 350, 365,
+ 380, 395, 410, 425, 440, 455, 470, 485, 500, 351, 366, 381, 396, 411, 426, 441, 456, 471, 486, 501, 367, 382, 397,
+ 412, 427, 442, 457, 472, 487, 502, 383, 398, 413, 428, 443, 458, 473, 488, 503, 399, 414, 429, 444, 459, 474, 489,
+ 504, 415, 430, 445, 460, 475, 490, 505, 431, 446, 461, 476, 491, 506, 447, 462, 477, 492, 507, 463, 478, 493, 508,
+ 479, 494, 509, 495, 510, 511,];
+
+ private static readonly short[] DefaultScan32x16 = [
+ 0, 32, 1, 64, 33, 2, 96, 65, 34, 3, 128, 97, 66, 35, 4, 160, 129, 98, 67, 36, 5, 192, 161,
+ 130, 99, 68, 37, 6, 224, 193, 162, 131, 100, 69, 38, 7, 256, 225, 194, 163, 132, 101, 70, 39, 8, 288,
+ 257, 226, 195, 164, 133, 102, 71, 40, 9, 320, 289, 258, 227, 196, 165, 134, 103, 72, 41, 10, 352, 321, 290,
+ 259, 228, 197, 166, 135, 104, 73, 42, 11, 384, 353, 322, 291, 260, 229, 198, 167, 136, 105, 74, 43, 12, 416,
+ 385, 354, 323, 292, 261, 230, 199, 168, 137, 106, 75, 44, 13, 448, 417, 386, 355, 324, 293, 262, 231, 200, 169,
+ 138, 107, 76, 45, 14, 480, 449, 418, 387, 356, 325, 294, 263, 232, 201, 170, 139, 108, 77, 46, 15, 481, 450,
+ 419, 388, 357, 326, 295, 264, 233, 202, 171, 140, 109, 78, 47, 16, 482, 451, 420, 389, 358, 327, 296, 265, 234,
+ 203, 172, 141, 110, 79, 48, 17, 483, 452, 421, 390, 359, 328, 297, 266, 235, 204, 173, 142, 111, 80, 49, 18,
+ 484, 453, 422, 391, 360, 329, 298, 267, 236, 205, 174, 143, 112, 81, 50, 19, 485, 454, 423, 392, 361, 330, 299,
+ 268, 237, 206, 175, 144, 113, 82, 51, 20, 486, 455, 424, 393, 362, 331, 300, 269, 238, 207, 176, 145, 114, 83,
+ 52, 21, 487, 456, 425, 394, 363, 332, 301, 270, 239, 208, 177, 146, 115, 84, 53, 22, 488, 457, 426, 395, 364,
+ 333, 302, 271, 240, 209, 178, 147, 116, 85, 54, 23, 489, 458, 427, 396, 365, 334, 303, 272, 241, 210, 179, 148,
+ 117, 86, 55, 24, 490, 459, 428, 397, 366, 335, 304, 273, 242, 211, 180, 149, 118, 87, 56, 25, 491, 460, 429,
+ 398, 367, 336, 305, 274, 243, 212, 181, 150, 119, 88, 57, 26, 492, 461, 430, 399, 368, 337, 306, 275, 244, 213,
+ 182, 151, 120, 89, 58, 27, 493, 462, 431, 400, 369, 338, 307, 276, 245, 214, 183, 152, 121, 90, 59, 28, 494,
+ 463, 432, 401, 370, 339, 308, 277, 246, 215, 184, 153, 122, 91, 60, 29, 495, 464, 433, 402, 371, 340, 309, 278,
+ 247, 216, 185, 154, 123, 92, 61, 30, 496, 465, 434, 403, 372, 341, 310, 279, 248, 217, 186, 155, 124, 93, 62,
+ 31, 497, 466, 435, 404, 373, 342, 311, 280, 249, 218, 187, 156, 125, 94, 63, 498, 467, 436, 405, 374, 343, 312,
+ 281, 250, 219, 188, 157, 126, 95, 499, 468, 437, 406, 375, 344, 313, 282, 251, 220, 189, 158, 127, 500, 469, 438,
+ 407, 376, 345, 314, 283, 252, 221, 190, 159, 501, 470, 439, 408, 377, 346, 315, 284, 253, 222, 191, 502, 471, 440,
+ 409, 378, 347, 316, 285, 254, 223, 503, 472, 441, 410, 379, 348, 317, 286, 255, 504, 473, 442, 411, 380, 349, 318,
+ 287, 505, 474, 443, 412, 381, 350, 319, 506, 475, 444, 413, 382, 351, 507, 476, 445, 414, 383, 508, 477, 446, 415,
+ 509, 478, 447, 510, 479, 511,];
+
+ private static readonly short[] DefaultScan4x16 = [
+ 0, 1, 4, 2, 5, 8, 3, 6, 9, 12, 7, 10, 13, 16, 11, 14, 17, 20, 15, 18, 21, 24,
+ 19, 22, 25, 28, 23, 26, 29, 32, 27, 30, 33, 36, 31, 34, 37, 40, 35, 38, 41, 44, 39, 42,
+ 45, 48, 43, 46, 49, 52, 47, 50, 53, 56, 51, 54, 57, 60, 55, 58, 61, 59, 62, 63,];
+
+ private static readonly short[] DefaultScan16x4 = [
+ 0, 16, 1, 32, 17, 2, 48, 33, 18, 3, 49, 34, 19, 4, 50, 35, 20, 5, 51, 36, 21, 6,
+ 52, 37, 22, 7, 53, 38, 23, 8, 54, 39, 24, 9, 55, 40, 25, 10, 56, 41, 26, 11, 57, 42,
+ 27, 12, 58, 43, 28, 13, 59, 44, 29, 14, 60, 45, 30, 15, 61, 46, 31, 62, 47, 63,];
+
+ private static readonly short[] DefaultScan8x32 = [
+ 0, 1, 8, 2, 9, 16, 3, 10, 17, 24, 4, 11, 18, 25, 32, 5, 12, 19, 26, 33, 40, 6,
+ 13, 20, 27, 34, 41, 48, 7, 14, 21, 28, 35, 42, 49, 56, 15, 22, 29, 36, 43, 50, 57, 64,
+ 23, 30, 37, 44, 51, 58, 65, 72, 31, 38, 45, 52, 59, 66, 73, 80, 39, 46, 53, 60, 67, 74,
+ 81, 88, 47, 54, 61, 68, 75, 82, 89, 96, 55, 62, 69, 76, 83, 90, 97, 104, 63, 70, 77, 84,
+ 91, 98, 105, 112, 71, 78, 85, 92, 99, 106, 113, 120, 79, 86, 93, 100, 107, 114, 121, 128, 87, 94,
+ 101, 108, 115, 122, 129, 136, 95, 102, 109, 116, 123, 130, 137, 144, 103, 110, 117, 124, 131, 138, 145, 152,
+ 111, 118, 125, 132, 139, 146, 153, 160, 119, 126, 133, 140, 147, 154, 161, 168, 127, 134, 141, 148, 155, 162,
+ 169, 176, 135, 142, 149, 156, 163, 170, 177, 184, 143, 150, 157, 164, 171, 178, 185, 192, 151, 158, 165, 172,
+ 179, 186, 193, 200, 159, 166, 173, 180, 187, 194, 201, 208, 167, 174, 181, 188, 195, 202, 209, 216, 175, 182,
+ 189, 196, 203, 210, 217, 224, 183, 190, 197, 204, 211, 218, 225, 232, 191, 198, 205, 212, 219, 226, 233, 240,
+ 199, 206, 213, 220, 227, 234, 241, 248, 207, 214, 221, 228, 235, 242, 249, 215, 222, 229, 236, 243, 250, 223,
+ 230, 237, 244, 251, 231, 238, 245, 252, 239, 246, 253, 247, 254, 255,];
+
+ private static readonly short[] DefaultScan32x8 = [
+ 0, 32, 1, 64, 33, 2, 96, 65, 34, 3, 128, 97, 66, 35, 4, 160, 129, 98, 67, 36, 5, 192,
+ 161, 130, 99, 68, 37, 6, 224, 193, 162, 131, 100, 69, 38, 7, 225, 194, 163, 132, 101, 70, 39, 8,
+ 226, 195, 164, 133, 102, 71, 40, 9, 227, 196, 165, 134, 103, 72, 41, 10, 228, 197, 166, 135, 104, 73,
+ 42, 11, 229, 198, 167, 136, 105, 74, 43, 12, 230, 199, 168, 137, 106, 75, 44, 13, 231, 200, 169, 138,
+ 107, 76, 45, 14, 232, 201, 170, 139, 108, 77, 46, 15, 233, 202, 171, 140, 109, 78, 47, 16, 234, 203,
+ 172, 141, 110, 79, 48, 17, 235, 204, 173, 142, 111, 80, 49, 18, 236, 205, 174, 143, 112, 81, 50, 19,
+ 237, 206, 175, 144, 113, 82, 51, 20, 238, 207, 176, 145, 114, 83, 52, 21, 239, 208, 177, 146, 115, 84,
+ 53, 22, 240, 209, 178, 147, 116, 85, 54, 23, 241, 210, 179, 148, 117, 86, 55, 24, 242, 211, 180, 149,
+ 118, 87, 56, 25, 243, 212, 181, 150, 119, 88, 57, 26, 244, 213, 182, 151, 120, 89, 58, 27, 245, 214,
+ 183, 152, 121, 90, 59, 28, 246, 215, 184, 153, 122, 91, 60, 29, 247, 216, 185, 154, 123, 92, 61, 30,
+ 248, 217, 186, 155, 124, 93, 62, 31, 249, 218, 187, 156, 125, 94, 63, 250, 219, 188, 157, 126, 95, 251,
+ 220, 189, 158, 127, 252, 221, 190, 159, 253, 222, 191, 254, 223, 255,];
+
+ private static readonly short[] MatrixColumnScan4x4 = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
+ private static readonly short[] MatrixColumnScan8x8 = [
+ 0, 8, 16, 24, 32, 40, 48, 56, 1, 9, 17, 25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42,
+ 50, 58, 3, 11, 19, 27, 35, 43, 51, 59, 4, 12, 20, 28, 36, 44, 52, 60, 5, 13, 21, 29,
+ 37, 45, 53, 61, 6, 14, 22, 30, 38, 46, 54, 62, 7, 15, 23, 31, 39, 47, 55, 63];
+
+ private static readonly short[] MatrixColumnScan16x16 = [
+ 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 1, 17, 33, 49, 65, 81,
+ 97, 113, 129, 145, 161, 177, 193, 209, 225, 241, 2, 18, 34, 50, 66, 82, 98, 114, 130, 146, 162, 178,
+ 194, 210, 226, 242, 3, 19, 35, 51, 67, 83, 99, 115, 131, 147, 163, 179, 195, 211, 227, 243, 4, 20,
+ 36, 52, 68, 84, 100, 116, 132, 148, 164, 180, 196, 212, 228, 244, 5, 21, 37, 53, 69, 85, 101, 117,
+ 133, 149, 165, 181, 197, 213, 229, 245, 6, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214,
+ 230, 246, 7, 23, 39, 55, 71, 87, 103, 119, 135, 151, 167, 183, 199, 215, 231, 247, 8, 24, 40, 56,
+ 72, 88, 104, 120, 136, 152, 168, 184, 200, 216, 232, 248, 9, 25, 41, 57, 73, 89, 105, 121, 137, 153,
+ 169, 185, 201, 217, 233, 249, 10, 26, 42, 58, 74, 90, 106, 122, 138, 154, 170, 186, 202, 218, 234, 250,
+ 11, 27, 43, 59, 75, 91, 107, 123, 139, 155, 171, 187, 203, 219, 235, 251, 12, 28, 44, 60, 76, 92,
+ 108, 124, 140, 156, 172, 188, 204, 220, 236, 252, 13, 29, 45, 61, 77, 93, 109, 125, 141, 157, 173, 189,
+ 205, 221, 237, 253, 14, 30, 46, 62, 78, 94, 110, 126, 142, 158, 174, 190, 206, 222, 238, 254, 15, 31,
+ 47, 63, 79, 95, 111, 127, 143, 159, 175, 191, 207, 223, 239, 255,];
+
+ private static readonly short[] MatrixColumnScan4x8 = [
+ 0, 4, 8, 12, 16, 20, 24, 28, 1, 5, 9, 13, 17, 21, 25, 29,
+ 2, 6, 10, 14, 18, 22, 26, 30, 3, 7, 11, 15, 19, 23, 27, 31,];
+
+ private static readonly short[] MatrixColumnScan8x4 = [
+ 0, 8, 16, 24, 1, 9, 17, 25, 2, 10, 18, 26, 3, 11, 19, 27,
+ 4, 12, 20, 28, 5, 13, 21, 29, 6, 14, 22, 30, 7, 15, 23, 31,];
+
+ private static readonly short[] MatrixColumnScan8x16 = [
+ 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 1, 9, 17, 25, 33, 41,
+ 49, 57, 65, 73, 81, 89, 97, 105, 113, 121, 2, 10, 18, 26, 34, 42, 50, 58, 66, 74, 82, 90,
+ 98, 106, 114, 122, 3, 11, 19, 27, 35, 43, 51, 59, 67, 75, 83, 91, 99, 107, 115, 123, 4, 12,
+ 20, 28, 36, 44, 52, 60, 68, 76, 84, 92, 100, 108, 116, 124, 5, 13, 21, 29, 37, 45, 53, 61,
+ 69, 77, 85, 93, 101, 109, 117, 125, 6, 14, 22, 30, 38, 46, 54, 62, 70, 78, 86, 94, 102, 110,
+ 118, 126, 7, 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, 95, 103, 111, 119, 127,];
+
+ private static readonly short[] MatrixColumnScan16x8 = [
+ 0, 16, 32, 48, 64, 80, 96, 112, 1, 17, 33, 49, 65, 81, 97, 113, 2, 18, 34, 50, 66, 82,
+ 98, 114, 3, 19, 35, 51, 67, 83, 99, 115, 4, 20, 36, 52, 68, 84, 100, 116, 5, 21, 37, 53,
+ 69, 85, 101, 117, 6, 22, 38, 54, 70, 86, 102, 118, 7, 23, 39, 55, 71, 87, 103, 119, 8, 24,
+ 40, 56, 72, 88, 104, 120, 9, 25, 41, 57, 73, 89, 105, 121, 10, 26, 42, 58, 74, 90, 106, 122,
+ 11, 27, 43, 59, 75, 91, 107, 123, 12, 28, 44, 60, 76, 92, 108, 124, 13, 29, 45, 61, 77, 93,
+ 109, 125, 14, 30, 46, 62, 78, 94, 110, 126, 15, 31, 47, 63, 79, 95, 111, 127,];
+
+ private static readonly short[] MatrixColumnScan4x16 = [
+ 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 1, 5, 9, 13, 17, 21,
+ 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46,
+ 50, 54, 58, 62, 3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, 55, 59, 63,];
+
+ private static readonly short[] MatrixColumnScan16x4 = [
+ 0, 16, 32, 48, 1, 17, 33, 49, 2, 18, 34, 50, 3, 19, 35, 51, 4, 20, 36, 52, 5, 21,
+ 37, 53, 6, 22, 38, 54, 7, 23, 39, 55, 8, 24, 40, 56, 9, 25, 41, 57, 10, 26, 42, 58,
+ 11, 27, 43, 59, 12, 28, 44, 60, 13, 29, 45, 61, 14, 30, 46, 62, 15, 31, 47, 63,];
+
+ private static readonly short[] MatrixColumnScan8x32 = [
+ 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168,
+ 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89,
+ 97, 105, 113, 121, 129, 137, 145, 153, 161, 169, 177, 185, 193, 201, 209, 217, 225, 233, 241, 249, 2, 10,
+ 18, 26, 34, 42, 50, 58, 66, 74, 82, 90, 98, 106, 114, 122, 130, 138, 146, 154, 162, 170, 178, 186,
+ 194, 202, 210, 218, 226, 234, 242, 250, 3, 11, 19, 27, 35, 43, 51, 59, 67, 75, 83, 91, 99, 107,
+ 115, 123, 131, 139, 147, 155, 163, 171, 179, 187, 195, 203, 211, 219, 227, 235, 243, 251, 4, 12, 20, 28,
+ 36, 44, 52, 60, 68, 76, 84, 92, 100, 108, 116, 124, 132, 140, 148, 156, 164, 172, 180, 188, 196, 204,
+ 212, 220, 228, 236, 244, 252, 5, 13, 21, 29, 37, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125,
+ 133, 141, 149, 157, 165, 173, 181, 189, 197, 205, 213, 221, 229, 237, 245, 253, 6, 14, 22, 30, 38, 46,
+ 54, 62, 70, 78, 86, 94, 102, 110, 118, 126, 134, 142, 150, 158, 166, 174, 182, 190, 198, 206, 214, 222,
+ 230, 238, 246, 254, 7, 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, 95, 103, 111, 119, 127, 135, 143,
+ 151, 159, 167, 175, 183, 191, 199, 207, 215, 223, 231, 239, 247, 255,];
+
+ private static readonly short[] MatrixColumnScan32x8 = [
+ 0, 32, 64, 96, 128, 160, 192, 224, 1, 33, 65, 97, 129, 161, 193, 225, 2, 34, 66, 98, 130, 162, 194, 226,
+ 3, 35, 67, 99, 131, 163, 195, 227, 4, 36, 68, 100, 132, 164, 196, 228, 5, 37, 69, 101, 133, 165, 197, 229,
+ 6, 38, 70, 102, 134, 166, 198, 230, 7, 39, 71, 103, 135, 167, 199, 231, 8, 40, 72, 104, 136, 168, 200, 232,
+ 9, 41, 73, 105, 137, 169, 201, 233, 10, 42, 74, 106, 138, 170, 202, 234, 11, 43, 75, 107, 139, 171, 203, 235,
+ 12, 44, 76, 108, 140, 172, 204, 236, 13, 45, 77, 109, 141, 173, 205, 237, 14, 46, 78, 110, 142, 174, 206, 238,
+ 15, 47, 79, 111, 143, 175, 207, 239, 16, 48, 80, 112, 144, 176, 208, 240, 17, 49, 81, 113, 145, 177, 209, 241,
+ 18, 50, 82, 114, 146, 178, 210, 242, 19, 51, 83, 115, 147, 179, 211, 243, 20, 52, 84, 116, 148, 180, 212, 244,
+ 21, 53, 85, 117, 149, 181, 213, 245, 22, 54, 86, 118, 150, 182, 214, 246, 23, 55, 87, 119, 151, 183, 215, 247,
+ 24, 56, 88, 120, 152, 184, 216, 248, 25, 57, 89, 121, 153, 185, 217, 249, 26, 58, 90, 122, 154, 186, 218, 250,
+ 27, 59, 91, 123, 155, 187, 219, 251, 28, 60, 92, 124, 156, 188, 220, 252, 29, 61, 93, 125, 157, 189, 221, 253,
+ 30, 62, 94, 126, 158, 190, 222, 254, 31, 63, 95, 127, 159, 191, 223, 255,];
+
+ private static readonly short[] MatrixRowScan4x4 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+ private static readonly short[] MatrixRowScan8x8 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63];
+
+ private static readonly short[] MatrixRowScan16x16 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
+ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+ 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153,
+ 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+ 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197,
+ 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
+ 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
+ 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,];
+
+ private static readonly short[] MatrixRowScan4x8 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,];
+
+ private static readonly short[] MatrixRowScan8x4 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,];
+
+ private static readonly short[] MatrixRowScan8x16 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
+ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,];
+
+ private static readonly short[] MatrixRowScan16x8 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
+ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,];
+
+ private static readonly short[] MatrixRowScan4x16 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,];
+
+ private static readonly short[] MatrixRowScan16x4 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,];
+
+ private static readonly short[] MatrixRowScan8x32 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
+ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+ 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153,
+ 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+ 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197,
+ 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
+ 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
+ 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,];
+
+ private static readonly short[] MatrixRowScan32x8 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
+ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+ 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153,
+ 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+ 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197,
+ 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
+ 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
+ 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,];
+
+ // InverseScan is not used (yet) for AVIF coding, leave these arrays empty for now.
+ private static readonly short[] DefaultInverseScan4x4 = [];
+ private static readonly short[] DefaultInverseScan8x8 = [];
+ private static readonly short[] DefaultInverseScan16x16 = [];
+ private static readonly short[] DefaultInverseScan32x32 = [];
+ private static readonly short[] DefaultInverseScan64x64 = [];
+ private static readonly short[] DefaultInverseScan4x8 = [];
+ private static readonly short[] DefaultInverseScan8x4 = [];
+ private static readonly short[] DefaultInverseScan8x16 = [];
+ private static readonly short[] DefaultInverseScan16x8 = [];
+ private static readonly short[] DefaultInverseScan16x32 = [];
+ private static readonly short[] DefaultInverseScan32x16 = [];
+ private static readonly short[] DefaultInverseScan4x16 = [];
+ private static readonly short[] DefaultInverseScan16x4 = [];
+ private static readonly short[] DefaultInverseScan8x32 = [];
+ private static readonly short[] DefaultInverseScan32x8 = [];
+
+ private static readonly short[] MatrixColumnInverseScan4x4 = [];
+ private static readonly short[] MatrixColumnInverseScan8x8 = [];
+ private static readonly short[] MatrixColumnInverseScan16x16 = [];
+ private static readonly short[] MatrixColumnInverseScan32x32 = [];
+ private static readonly short[] MatrixColumnInverseScan64x64 = [];
+ private static readonly short[] MatrixColumnInverseScan4x8 = [];
+ private static readonly short[] MatrixColumnInverseScan8x4 = [];
+ private static readonly short[] MatrixColumnInverseScan8x16 = [];
+ private static readonly short[] MatrixColumnInverseScan16x8 = [];
+ private static readonly short[] MatrixColumnInverseScan16x32 = [];
+ private static readonly short[] MatrixColumnInverseScan32x16 = [];
+ private static readonly short[] MatrixColumnInverseScan4x16 = [];
+ private static readonly short[] MatrixColumnInverseScan16x4 = [];
+ private static readonly short[] MatrixColumnInverseScan8x32 = [];
+ private static readonly short[] MatrixColumnInverseScan32x8 = [];
+
+ private static readonly short[] MatrixRowInverseScan4x4 = [];
+ private static readonly short[] MatrixRowInverseScan8x8 = [];
+ private static readonly short[] MatrixRowInverseScan16x16 = [];
+ private static readonly short[] MatrixRowInverseScan32x32 = [];
+ private static readonly short[] MatrixRowInverseScan64x64 = [];
+ private static readonly short[] MatrixRowInverseScan4x8 = [];
+ private static readonly short[] MatrixRowInverseScan8x4 = [];
+ private static readonly short[] MatrixRowInverseScan8x16 = [];
+ private static readonly short[] MatrixRowInverseScan16x8 = [];
+ private static readonly short[] MatrixRowInverseScan16x32 = [];
+ private static readonly short[] MatrixRowInverseScan32x16 = [];
+ private static readonly short[] MatrixRowInverseScan4x16 = [];
+ private static readonly short[] MatrixRowInverseScan16x4 = [];
+ private static readonly short[] MatrixRowInverseScan8x32 = [];
+ private static readonly short[] MatrixRowInverseScan32x8 = [];
+
+ // Neighborss are not used (yet) for AVIF coding, leave these arrays empty for now.
+ private static readonly short[] DefaultScan4x4Neighbors = [];
+ private static readonly short[] DefaultScan8x8Neighbors = [];
+ private static readonly short[] DefaultScan16x16Neighbors = [];
+ private static readonly short[] DefaultScan32x32Neighbors = [];
+ private static readonly short[] DefaultScan64x64Neighbors = [];
+ private static readonly short[] DefaultScan4x8Neighbors = [];
+ private static readonly short[] DefaultScan8x4Neighbors = [];
+ private static readonly short[] DefaultScan8x16Neighbors = [];
+ private static readonly short[] DefaultScan16x8Neighbors = [];
+ private static readonly short[] DefaultScan16x32Neighbors = [];
+ private static readonly short[] DefaultScan32x16Neighbors = [];
+ private static readonly short[] DefaultScan4x16Neighbors = [];
+ private static readonly short[] DefaultScan16x4Neighbors = [];
+ private static readonly short[] DefaultScan8x32Neighbors = [];
+ private static readonly short[] DefaultScan32x8Neighbors = [];
+
+ private static readonly short[] MatrixColumnScan4x4Neighbors = [];
+ private static readonly short[] MatrixColumnScan8x8Neighbors = [];
+ private static readonly short[] MatrixColumnScan16x16Neighbors = [];
+ private static readonly short[] MatrixColumnScan32x32Neighbors = [];
+ private static readonly short[] MatrixColumnScan64x64Neighbors = [];
+ private static readonly short[] MatrixColumnScan4x8Neighbors = [];
+ private static readonly short[] MatrixColumnScan8x4Neighbors = [];
+ private static readonly short[] MatrixColumnScan8x16Neighbors = [];
+ private static readonly short[] MatrixColumnScan16x8Neighbors = [];
+ private static readonly short[] MatrixColumnScan16x32Neighbors = [];
+ private static readonly short[] MatrixColumnScan32x16Neighbors = [];
+ private static readonly short[] MatrixColumnScan4x16Neighbors = [];
+ private static readonly short[] MatrixColumnScan16x4Neighbors = [];
+ private static readonly short[] MatrixColumnScan8x32Neighbors = [];
+ private static readonly short[] MatrixColumnScan32x8Neighbors = [];
+
+ private static readonly short[] MatrixRowScan4x4Neighbors = [];
+ private static readonly short[] MatrixRowScan8x8Neighbors = [];
+ private static readonly short[] MatrixRowScan16x16Neighbors = [];
+ private static readonly short[] MatrixRowScan32x32Neighbors = [];
+ private static readonly short[] MatrixRowScan64x64Neighbors = [];
+ private static readonly short[] MatrixRowScan4x8Neighbors = [];
+ private static readonly short[] MatrixRowScan8x4Neighbors = [];
+ private static readonly short[] MatrixRowScan8x16Neighbors = [];
+ private static readonly short[] MatrixRowScan16x8Neighbors = [];
+ private static readonly short[] MatrixRowScan16x32Neighbors = [];
+ private static readonly short[] MatrixRowScan32x16Neighbors = [];
+ private static readonly short[] MatrixRowScan4x16Neighbors = [];
+ private static readonly short[] MatrixRowScan16x4Neighbors = [];
+ private static readonly short[] MatrixRowScan8x32Neighbors = [];
+ private static readonly short[] MatrixRowScan32x8Neighbors = [];
+
+ private static readonly Av1ScanOrder[][] ScanOrders =
+ [
+
+ // Transform size 4x4
+ [
+ new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors),
+ new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors),
+ new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors),
+ new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors),
+ new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors),
+ new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors),
+ new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors),
+ new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors),
+ new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors),
+ new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors),
+ new(MatrixRowScan4x4, MatrixRowInverseScan4x4, MatrixRowScan4x4Neighbors),
+ new(MatrixColumnScan4x4, MatrixColumnInverseScan4x4, MatrixColumnScan4x4Neighbors),
+ new(MatrixRowScan4x4, MatrixRowInverseScan4x4, MatrixRowScan4x4Neighbors),
+ new(MatrixColumnScan4x4, MatrixColumnInverseScan4x4, MatrixColumnScan4x4Neighbors),
+ new(MatrixRowScan4x4, MatrixRowInverseScan4x4, MatrixRowScan4x4Neighbors),
+ new(MatrixColumnScan4x4, MatrixColumnInverseScan4x4, MatrixColumnScan4x4Neighbors),
+ ],
+
+ // Transform size 8x8
+ [
+ new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors),
+ new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors),
+ new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors),
+ new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors),
+ new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors),
+ new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors),
+ new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors),
+ new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors),
+ new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors),
+ new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors),
+ new(MatrixRowScan8x8, MatrixRowInverseScan8x8, MatrixRowScan8x8Neighbors),
+ new(MatrixColumnScan8x8, MatrixColumnInverseScan8x8, MatrixColumnScan8x8Neighbors),
+ new(MatrixRowScan8x8, MatrixRowInverseScan8x8, MatrixRowScan8x8Neighbors),
+ new(MatrixColumnScan8x8, MatrixColumnInverseScan8x8, MatrixColumnScan8x8Neighbors),
+ new(MatrixRowScan8x8, MatrixRowInverseScan8x8, MatrixRowScan8x8Neighbors),
+ new(MatrixColumnScan8x8, MatrixColumnInverseScan8x8, MatrixColumnScan8x8Neighbors),
+ ],
+
+ // Transform size 16x16
+ [
+ new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors),
+ new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors),
+ new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors),
+ new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors),
+ new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors),
+ new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors),
+ new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors),
+ new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors),
+ new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors),
+ new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors),
+ new(MatrixRowScan16x16, MatrixRowInverseScan16x16, MatrixRowScan16x16Neighbors),
+ new(MatrixColumnScan16x16, MatrixColumnInverseScan16x16, MatrixColumnScan16x16Neighbors),
+ new(MatrixRowScan16x16, MatrixRowInverseScan16x16, MatrixRowScan16x16Neighbors),
+ new(MatrixColumnScan16x16, MatrixColumnInverseScan16x16, MatrixColumnScan16x16Neighbors),
+ new(MatrixRowScan16x16, MatrixRowInverseScan16x16, MatrixRowScan16x16Neighbors),
+ new(MatrixColumnScan16x16, MatrixColumnInverseScan16x16, MatrixColumnScan16x16Neighbors),
+ ],
+
+ // Transform size 32x32
+ [
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ ],
+ [
+
+ // Transform size 64X64
+ // Half of the coefficients of tx64 at higher frequencies are set to
+ // zeros. So tx32's scan order is used.
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ ],
+ [
+
+ // Transform size 4X8
+ new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors),
+ new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors),
+ new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors),
+ new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors),
+ new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors),
+ new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors),
+ new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors),
+ new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors),
+ new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors),
+ new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors),
+ new(MatrixRowScan4x8, MatrixRowInverseScan4x8, MatrixRowScan4x8Neighbors),
+ new(MatrixColumnScan4x8, MatrixColumnInverseScan4x8, MatrixColumnScan4x8Neighbors),
+ new(MatrixRowScan4x8, MatrixRowInverseScan4x8, MatrixRowScan4x8Neighbors),
+ new(MatrixColumnScan4x8, MatrixColumnInverseScan4x8, MatrixColumnScan4x8Neighbors),
+ new(MatrixRowScan4x8, MatrixRowInverseScan4x8, MatrixRowScan4x8Neighbors),
+ new(MatrixColumnScan4x8, MatrixColumnInverseScan4x8, MatrixColumnScan4x8Neighbors),
+ ],
+ [
+
+ // Transform size 8X4
+ new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors),
+ new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors),
+ new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors),
+ new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors),
+ new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors),
+ new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors),
+ new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors),
+ new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors),
+ new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors),
+ new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors),
+ new(MatrixRowScan8x4, MatrixRowInverseScan8x4, MatrixRowScan8x4Neighbors),
+ new(MatrixColumnScan8x4, MatrixColumnInverseScan8x4, MatrixColumnScan8x4Neighbors),
+ new(MatrixRowScan8x4, MatrixRowInverseScan8x4, MatrixRowScan8x4Neighbors),
+ new(MatrixColumnScan8x4, MatrixColumnInverseScan8x4, MatrixColumnScan8x4Neighbors),
+ new(MatrixRowScan8x4, MatrixRowInverseScan8x4, MatrixRowScan8x4Neighbors),
+ new(MatrixColumnScan8x4, MatrixColumnInverseScan8x4, MatrixColumnScan8x4Neighbors),
+ ],
+ [
+
+ // Transform size 8X16
+ new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors),
+ new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors),
+ new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors),
+ new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors),
+ new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors),
+ new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors),
+ new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors),
+ new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors),
+ new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors),
+ new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors),
+ new(MatrixRowScan8x16, MatrixRowInverseScan8x16, MatrixRowScan8x16Neighbors),
+ new(MatrixColumnScan8x16, MatrixColumnInverseScan8x16, MatrixColumnScan8x16Neighbors),
+ new(MatrixRowScan8x16, MatrixRowInverseScan8x16, MatrixRowScan8x16Neighbors),
+ new(MatrixColumnScan8x16, MatrixColumnInverseScan8x16, MatrixColumnScan8x16Neighbors),
+ new(MatrixRowScan8x16, MatrixRowInverseScan8x16, MatrixRowScan8x16Neighbors),
+ new(MatrixColumnScan8x16, MatrixColumnInverseScan8x16, MatrixColumnScan8x16Neighbors),
+ ],
+ [
+
+ // Transform size 16X8
+ new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors),
+ new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors),
+ new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors),
+ new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors),
+ new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors),
+ new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors),
+ new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors),
+ new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors),
+ new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors),
+ new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors),
+ new(MatrixRowScan16x8, MatrixRowInverseScan16x8, MatrixRowScan16x8Neighbors),
+ new(MatrixColumnScan16x8, MatrixColumnInverseScan16x8, MatrixColumnScan16x8Neighbors),
+ new(MatrixRowScan16x8, MatrixRowInverseScan16x8, MatrixRowScan16x8Neighbors),
+ new(MatrixColumnScan16x8, MatrixColumnInverseScan16x8, MatrixColumnScan16x8Neighbors),
+ new(MatrixRowScan16x8, MatrixRowInverseScan16x8, MatrixRowScan16x8Neighbors),
+ new(MatrixColumnScan16x8, MatrixColumnInverseScan16x8, MatrixColumnScan16x8Neighbors),
+ ],
+ [
+
+ // Transform size 16X32
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors),
+ ],
+ [
+
+ // Transform size 32X16
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors),
+ ],
+ [
+
+ // Transform size 32X64
+ // Half of the coefficients of tx64 at higher frequencies are set to
+ // zeros. So tx32's scan order is used.
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ ],
+ [
+
+ // Transform size 64X32
+ // Half of the coefficients of tx64 at higher frequencies are set to
+ // zeros. So tx32's scan order is used.
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors),
+ new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors),
+ ],
+ [
+
+ // Transform size 4X16
+ new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors),
+ new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors),
+ new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors),
+ new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors),
+ new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors),
+ new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors),
+ new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors),
+ new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors),
+ new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors),
+ new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors),
+ new(MatrixRowScan4x16, MatrixRowInverseScan4x16, MatrixRowScan4x16Neighbors),
+ new(MatrixColumnScan4x16, MatrixColumnInverseScan4x16, MatrixColumnScan4x16Neighbors),
+ new(MatrixRowScan4x16, MatrixRowInverseScan4x16, MatrixRowScan4x16Neighbors),
+ new(MatrixColumnScan4x16, MatrixColumnInverseScan4x16, MatrixColumnScan4x16Neighbors),
+ new(MatrixRowScan4x16, MatrixRowInverseScan4x16, MatrixRowScan4x16Neighbors),
+ new(MatrixColumnScan4x16, MatrixColumnInverseScan4x16, MatrixColumnScan4x16Neighbors),
+ ],
+ [
+
+ // Transform size 16X4
+ new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors),
+ new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors),
+ new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors),
+ new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors),
+ new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors),
+ new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors),
+ new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors),
+ new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors),
+ new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors),
+ new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors),
+ new(MatrixRowScan16x4, MatrixRowInverseScan16x4, MatrixRowScan16x4Neighbors),
+ new(MatrixColumnScan16x4, MatrixColumnInverseScan16x4, MatrixColumnScan16x4Neighbors),
+ new(MatrixRowScan16x4, MatrixRowInverseScan16x4, MatrixRowScan16x4Neighbors),
+ new(MatrixColumnScan16x4, MatrixColumnInverseScan16x4, MatrixColumnScan16x4Neighbors),
+ new(MatrixRowScan16x4, MatrixRowInverseScan16x4, MatrixRowScan16x4Neighbors),
+ new(MatrixColumnScan16x4, MatrixColumnInverseScan16x4, MatrixColumnScan16x4Neighbors),
+ ],
+ [
+
+ // Transform size 8X32
+ new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors),
+ new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors),
+ new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors),
+ new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors),
+ new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors),
+ new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors),
+ new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors),
+ new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors),
+ new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors),
+ new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors),
+ new(MatrixRowScan8x32, MatrixRowInverseScan8x32, MatrixRowScan8x32Neighbors),
+ new(MatrixColumnScan8x32, MatrixColumnInverseScan8x32, MatrixColumnScan8x32Neighbors),
+ new(MatrixRowScan8x32, MatrixRowInverseScan8x32, MatrixRowScan8x32Neighbors),
+ new(MatrixColumnScan8x32, MatrixColumnInverseScan8x32, MatrixColumnScan8x32Neighbors),
+ new(MatrixRowScan8x32, MatrixRowInverseScan8x32, MatrixRowScan8x32Neighbors),
+ new(MatrixColumnScan8x32, MatrixColumnInverseScan8x32, MatrixColumnScan8x32Neighbors),
+ ],
+ [
+
+ // Transform size 32X8
+ new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors),
+ new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors),
+ new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors),
+ new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors),
+ new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors),
+ new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors),
+ new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors),
+ new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors),
+ new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors),
+ new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors),
+ new(MatrixRowScan32x8, MatrixRowInverseScan32x8, MatrixRowScan32x8Neighbors),
+ new(MatrixColumnScan32x8, MatrixColumnInverseScan32x8, MatrixColumnScan32x8Neighbors),
+ new(MatrixRowScan32x8, MatrixRowInverseScan32x8, MatrixRowScan32x8Neighbors),
+ new(MatrixColumnScan32x8, MatrixColumnInverseScan32x8, MatrixColumnScan32x8Neighbors),
+ new(MatrixRowScan32x8, MatrixRowInverseScan32x8, MatrixRowScan32x8Neighbors),
+ new(MatrixColumnScan32x8, MatrixColumnInverseScan32x8, MatrixColumnScan32x8Neighbors),
+ ],
+ [
+
+ // Transform size 16X64
+ // Half of the coefficients of tx64 at higher frequencies are set to
+ // zeros. So tx32's scan order is used.
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors),
+ new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors),
+ ],
+ [
+
+ // Transform size 64X16
+ // Half of the coefficients of tx64 at higher frequencies are set to
+ // zeros. So tx32's scan order is used.
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors),
+ new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors),
+ ]
+ ];
+
+ public static Av1ScanOrder GetScanOrder(Av1TransformSize transformSize, Av1TransformType transformType)
+ => ScanOrders[(int)transformSize][(int)transformType];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs
new file mode 100644
index 0000000000..241730c6b2
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal static class Av1SinusConstants
+{
+ public const int MinimumCosinusBit = 10;
+
+ // av1_cospi_arr[i][j] = (int32_t)round(cos(M_PI*j/128) * (1<<(cos_bit_min+i)));
+ private static readonly int[][] CosinusPiArray =
+ [
+ [
+ 1024, 1024, 1023, 1021, 1019, 1016, 1013, 1009, 1004, 999, 993, 987, 980, 972, 964, 955,
+ 946, 936, 926, 915, 903, 891, 878, 865, 851, 837, 822, 807, 792, 775, 759, 742,
+ 724, 706, 688, 669, 650, 630, 610, 590, 569, 548, 526, 505, 483, 460, 438, 415,
+ 392, 369, 345, 321, 297, 273, 249, 224, 200, 175, 150, 125, 100, 75, 50, 25
+ ],
+ [
+ 2048, 2047, 2046, 2042, 2038, 2033, 2026, 2018, 2009, 1998, 1987, 1974, 1960, 1945, 1928, 1911,
+ 1892, 1872, 1851, 1829, 1806, 1782, 1757, 1730, 1703, 1674, 1645, 1615, 1583, 1551, 1517, 1483,
+ 1448, 1412, 1375, 1338, 1299, 1260, 1220, 1179, 1138, 1096, 1053, 1009, 965, 921, 876, 830,
+ 784, 737, 690, 642, 595, 546, 498, 449, 400, 350, 301, 251, 201, 151, 100, 50
+ ],
+ [
+ 4096, 4095, 4091, 4085, 4076, 4065, 4052, 4036, 4017, 3996, 3973, 3948, 3920, 3889, 3857, 3822,
+ 3784, 3745, 3703, 3659, 3612, 3564, 3513, 3461, 3406, 3349, 3290, 3229, 3166, 3102, 3035, 2967,
+ 2896, 2824, 2751, 2675, 2598, 2520, 2440, 2359, 2276, 2191, 2106, 2019, 1931, 1842, 1751, 1660,
+ 1567, 1474, 1380, 1285, 1189, 1092, 995, 897, 799, 700, 601, 501, 401, 301, 201, 101
+ ],
+ [
+ 8192, 8190, 8182, 8170, 8153, 8130, 8103, 8071, 8035, 7993, 7946, 7895, 7839, 7779, 7713, 7643,
+ 7568, 7489, 7405, 7317, 7225, 7128, 7027, 6921, 6811, 6698, 6580, 6458, 6333, 6203, 6070, 5933,
+ 5793, 5649, 5501, 5351, 5197, 5040, 4880, 4717, 4551, 4383, 4212, 4038, 3862, 3683, 3503, 3320,
+ 3135, 2948, 2760, 2570, 2378, 2185, 1990, 1795, 1598, 1401, 1202, 1003, 803, 603, 402, 201
+ ],
+ [
+ 16384, 16379, 16364, 16340, 16305, 16261, 16207, 16143, 16069, 15986, 15893, 15791, 15679, 15557, 15426, 15286,
+ 15137, 14978, 14811, 14635, 14449, 14256, 14053, 13842, 13623, 13395, 13160, 12916, 12665, 12406, 12140, 11866,
+ 11585, 11297, 11003, 10702, 10394, 10080, 9760, 9434, 9102, 8765, 8423, 8076, 7723, 7366, 7005, 6639,
+ 6270, 5897, 5520, 5139, 4756, 4370, 3981, 3590, 3196, 2801, 2404, 2006, 1606, 1205, 804, 402
+ ],
+ [
+ 32768, 32758, 32729, 32679, 32610, 32522, 32413, 32286, 32138, 31972, 31786, 31581, 31357, 31114, 30853, 30572,
+ 30274, 29957, 29622, 29269, 28899, 28511, 28106, 27684, 27246, 26791, 26320, 25833, 25330, 24812, 24279, 23732,
+ 23170, 22595, 22006, 21403, 20788, 20160, 19520, 18868, 18205, 17531, 16846, 16151, 15447, 14733, 14010, 13279,
+ 12540, 11793, 11039, 10279, 9512, 8740, 7962, 7180, 6393, 5602, 4808, 4011, 3212, 2411, 1608, 804
+ ],
+ [
+ 65536, 65516, 65457, 65358, 65220, 65043, 64827, 64571, 64277, 63944, 63572, 63162, 62714, 62228, 61705, 61145,
+ 60547, 59914, 59244, 58538, 57798, 57022, 56212, 55368, 54491, 53581, 52639, 51665, 50660, 49624, 48559, 47464,
+ 46341, 45190, 44011, 42806, 41576, 40320, 39040, 37736, 36410, 35062, 33692, 32303, 30893, 29466, 28020, 26558,
+ 25080, 23586, 22078, 20557, 19024, 17479, 15924, 14359, 12785, 11204, 9616, 8022, 6424, 4821, 3216, 1608
+ ]
+ ];
+
+ // svt_aom_eb_av1_sinpi_arr_data[i][j] = (int32_t)round((sqrt(2) * sin(j*Pi/9) * 2 / 3) * (1
+ // << (cos_bit_min + i))) modified so that elements j=1,2 sum to element j=4.
+ private static readonly int[][] SinusPiArray =
+ [
+ [0, 330, 621, 836, 951],
+ [0, 660, 1241, 1672, 1901],
+ [0, 1321, 2482, 3344, 3803],
+ [0, 2642, 4964, 6689, 7606],
+ [0, 5283, 9929, 13377, 15212],
+ [0, 10566, 19858, 26755, 30424],
+ [0, 21133, 39716, 53510, 60849]
+ ];
+
+ public static Span CosinusPi(int n) => CosinusPiArray[n - MinimumCosinusBit];
+
+ public static Span SinusPi(int n) => SinusPiArray[n - MinimumCosinusBit];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs
new file mode 100644
index 0000000000..b1abf23245
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs
@@ -0,0 +1,326 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal class Av1Transform2dFlipConfiguration
+{
+ public const int MaxStageNumber = 12;
+ private const int SmallestTransformSizeLog2 = 2;
+
+ private static readonly Av1TransformType1d[] VerticalType =
+ [
+ Av1TransformType1d.Dct,
+ Av1TransformType1d.Adst,
+ Av1TransformType1d.Dct,
+ Av1TransformType1d.Adst,
+ Av1TransformType1d.FlipAdst,
+ Av1TransformType1d.Dct,
+ Av1TransformType1d.FlipAdst,
+ Av1TransformType1d.Adst,
+ Av1TransformType1d.FlipAdst,
+ Av1TransformType1d.Identity,
+ Av1TransformType1d.Dct,
+ Av1TransformType1d.Identity,
+ Av1TransformType1d.Adst,
+ Av1TransformType1d.Identity,
+ Av1TransformType1d.FlipAdst,
+ Av1TransformType1d.Identity,
+ ];
+
+ private static readonly Av1TransformType1d[] HorizontalType =
+ [
+ Av1TransformType1d.Dct,
+ Av1TransformType1d.Dct,
+ Av1TransformType1d.Adst,
+ Av1TransformType1d.Adst,
+ Av1TransformType1d.Dct,
+ Av1TransformType1d.FlipAdst,
+ Av1TransformType1d.FlipAdst,
+ Av1TransformType1d.FlipAdst,
+ Av1TransformType1d.Adst,
+ Av1TransformType1d.Identity,
+ Av1TransformType1d.Identity,
+ Av1TransformType1d.Dct,
+ Av1TransformType1d.Identity,
+ Av1TransformType1d.Adst,
+ Av1TransformType1d.Identity,
+ Av1TransformType1d.FlipAdst,
+ ];
+
+ private static readonly int[][] ShiftMap =
+ [
+ [2, 0, 0], // 4x4
+ [2, -1, 0], // 8x8
+ [2, -2, 0], // 16x16
+ [2, -4, 0], // 32x32
+ [0, -2, -2], // 64x64
+ [2, -1, 0], // 4x8
+ [2, -1, 0], // 8x4
+ [2, -2, 0], // 8x16
+ [2, -2, 0], // 16x8
+ [2, -4, 0], // 16x32
+ [2, -4, 0], // 32x16
+ [0, -2, -2], // 32x64
+ [2, -4, -2], // 64x32
+ [2, -1, 0], // 4x16
+ [2, -1, 0], // 16x4
+ [2, -2, 0], // 8x32
+ [2, -2, 0], // 32x8
+ [0, -2, 0], // 16x64
+ [2, -4, 0], // 64x16
+ ];
+
+ private static readonly int[][] CosBitColumnMap =
+ [[13, 13, 13, 0, 0], [13, 13, 13, 12, 0], [13, 13, 13, 12, 13], [0, 13, 13, 12, 13], [0, 0, 13, 12, 13]];
+
+ private static readonly int[][] CosBitRowMap =
+ [[13, 13, 12, 0, 0], [13, 13, 13, 12, 0], [13, 13, 12, 13, 12], [0, 12, 13, 12, 11], [0, 0, 12, 11, 10]];
+
+ private static readonly Av1TransformFunctionType[][] TransformFunctionTypeMap =
+ [
+ [Av1TransformFunctionType.Dct4, Av1TransformFunctionType.Adst4, Av1TransformFunctionType.Adst4, Av1TransformFunctionType.Identity4],
+ [Av1TransformFunctionType.Dct8, Av1TransformFunctionType.Adst8, Av1TransformFunctionType.Adst8, Av1TransformFunctionType.Identity8],
+ [Av1TransformFunctionType.Dct16, Av1TransformFunctionType.Adst16, Av1TransformFunctionType.Adst16, Av1TransformFunctionType.Identity16],
+ [Av1TransformFunctionType.Dct32, Av1TransformFunctionType.Adst32, Av1TransformFunctionType.Adst32, Av1TransformFunctionType.Identity32],
+ [Av1TransformFunctionType.Dct64, Av1TransformFunctionType.Invalid, Av1TransformFunctionType.Invalid, Av1TransformFunctionType.Identity64]
+ ];
+
+ private static readonly int[] StageNumberList =
+ [
+ 4, // TXFM_TYPE_DCT4
+ 6, // TXFM_TYPE_DCT8
+ 8, // TXFM_TYPE_DCT16
+ 10, // TXFM_TYPE_DCT32
+ 12, // TXFM_TYPE_DCT64
+ 7, // TXFM_TYPE_ADST4
+ 8, // TXFM_TYPE_ADST8
+ 10, // TXFM_TYPE_ADST16
+ 12, // TXFM_TYPE_ADST32
+ 1, // TXFM_TYPE_IDENTITY4
+ 1, // TXFM_TYPE_IDENTITY8
+ 1, // TXFM_TYPE_IDENTITY16
+ 1, // TXFM_TYPE_IDENTITY32
+ 1, // TXFM_TYPE_IDENTITY64
+ ];
+
+ private static readonly int[][] RangeMulti2List =
+ [
+ [0, 2, 3, 3], // fdct4_range_mult2
+ [0, 2, 4, 5, 5, 5], // fdct8_range_mult2
+ [0, 2, 4, 6, 7, 7, 7, 7], // fdct16_range_mult2
+ [0, 2, 4, 6, 8, 9, 9, 9, 9, 9], // fdct32_range_mult2
+ [0, 2, 4, 6, 8, 10, 11, 11, 11, 11, 11, 11], // fdct64_range_mult2
+ [0, 2, 4, 3, 3, 3, 3], // fadst4_range_mult2
+ [0, 0, 1, 3, 3, 5, 5, 5], // fadst8_range_mult2
+ [0, 0, 1, 3, 3, 5, 5, 7, 7, 7], // fadst16_range_mult2
+ [0, 0, 1, 3, 3, 5, 5, 7, 7, 9, 9, 9], // fadst32_range_mult2
+ [1], // fidtx4_range_mult2
+ [2], // fidtx8_range_mult2
+ [3], // fidtx16_range_mult2
+ [4], // fidtx32_range_mult2
+ [5], // fidtx64_range_mult2
+ ];
+
+ private int[] shift;
+
+ public Av1Transform2dFlipConfiguration(Av1TransformType transformType, Av1TransformSize transformSize)
+ {
+ // SVT: svt_av1_get_inv_txfm_cfg
+ // SVT: svt_aom_transform_config
+ this.TransformSize = transformSize;
+ this.TransformType = transformType;
+ this.SetFlip(transformType);
+ this.TransformTypeColumn = VerticalType[(int)transformType];
+ this.TransformTypeRow = HorizontalType[(int)transformType];
+ int txw_idx = transformSize.GetBlockWidthLog2() - SmallestTransformSizeLog2;
+ int txh_idx = transformSize.GetBlockHeightLog2() - SmallestTransformSizeLog2;
+ this.shift = ShiftMap[(int)transformSize];
+ this.CosBitColumn = CosBitColumnMap[txw_idx][txh_idx];
+ this.CosBitRow = CosBitRowMap[txw_idx][txh_idx];
+ this.TransformFunctionTypeColumn = TransformFunctionTypeMap[txh_idx][(int)this.TransformTypeColumn];
+ this.TransformFunctionTypeRow = TransformFunctionTypeMap[txw_idx][(int)this.TransformTypeRow];
+ this.StageNumberColumn = StageNumberList[(int)this.TransformFunctionTypeColumn];
+ this.StageNumberRow = StageNumberList[(int)this.TransformFunctionTypeRow];
+ this.StageRangeColumn = new byte[12];
+ this.StageRangeRow = new byte[12];
+ this.NonScaleRange();
+ }
+
+ public int CosBitColumn { get; }
+
+ public int CosBitRow { get; }
+
+ public Av1TransformType1d TransformTypeColumn { get; }
+
+ public Av1TransformType1d TransformTypeRow { get; }
+
+ public Av1TransformFunctionType TransformFunctionTypeColumn { get; }
+
+ public Av1TransformFunctionType TransformFunctionTypeRow { get; }
+
+ public int StageNumberColumn { get; }
+
+ public int StageNumberRow { get; }
+
+ public Av1TransformSize TransformSize { get; }
+
+ public Av1TransformType TransformType { get; }
+
+ public bool FlipUpsideDown { get; private set; }
+
+ public bool FlipLeftToRight { get; private set; }
+
+ public Span Shift => this.shift;
+
+ public byte[] StageRangeColumn { get; }
+
+ public byte[] StageRangeRow { get; }
+
+ ///
+ /// SVT: svt_av1_gen_fwd_stage_range
+ /// SVT: svt_av1_gen_inv_stage_range
+ ///
+ public void GenerateStageRange(int bitDepth)
+ {
+ // Take the shift from the larger dimension in the rectangular case.
+ Span shift = this.Shift;
+
+ // i < MAX_TXFM_STAGE_NUM will mute above array bounds warning
+ for (int i = 0; i < this.StageNumberColumn && i < MaxStageNumber; ++i)
+ {
+ this.StageRangeColumn[i] = (byte)(this.StageRangeColumn[i] + shift[0] + bitDepth + 1);
+ }
+
+ // i < MAX_TXFM_STAGE_NUM will mute above array bounds warning
+ for (int i = 0; i < this.StageNumberRow && i < MaxStageNumber; ++i)
+ {
+ this.StageRangeRow[i] = (byte)(this.StageRangeRow[i] + shift[0] + shift[1] + bitDepth + 1);
+ }
+ }
+
+ ///
+ /// SVT: is_txfm_allowed
+ ///
+ public bool IsAllowed()
+ {
+ Av1TransformType[] supportedTypes =
+ [
+ Av1TransformType.DctDct,
+ Av1TransformType.AdstDct,
+ Av1TransformType.DctAdst,
+ Av1TransformType.AdstAdst,
+ Av1TransformType.FlipAdstDct,
+ Av1TransformType.DctFlipAdst,
+ Av1TransformType.FlipAdstFlipAdst,
+ Av1TransformType.AdstFlipAdst,
+ Av1TransformType.FlipAdstAdst,
+ Av1TransformType.Identity,
+ Av1TransformType.VerticalDct,
+ Av1TransformType.HorizontalDct,
+ Av1TransformType.VerticalAdst,
+ Av1TransformType.HorizontalAdst,
+ Av1TransformType.VerticalFlipAdst,
+ Av1TransformType.HorizontalFlipAdst,
+ ];
+
+ switch (this.TransformSize)
+ {
+ case Av1TransformSize.Size32x32:
+ supportedTypes = [Av1TransformType.DctDct, Av1TransformType.Identity, Av1TransformType.VerticalDct, Av1TransformType.HorizontalDct];
+ break;
+ case Av1TransformSize.Size32x64:
+ case Av1TransformSize.Size64x32:
+ case Av1TransformSize.Size16x64:
+ case Av1TransformSize.Size64x16:
+ supportedTypes = [Av1TransformType.DctDct];
+ break;
+ case Av1TransformSize.Size16x32:
+ case Av1TransformSize.Size32x16:
+ case Av1TransformSize.Size64x64:
+ case Av1TransformSize.Size8x32:
+ case Av1TransformSize.Size32x8:
+ supportedTypes = [Av1TransformType.DctDct, Av1TransformType.Identity];
+ break;
+ default:
+ break;
+ }
+
+ return supportedTypes.Contains(this.TransformType);
+ }
+
+ internal void SetShift(int shift0, int shift1, int shift2) => this.shift = [shift0, shift1, shift2];
+
+ internal void SetFlip(bool upsideDown, bool leftToRight)
+ {
+ this.FlipUpsideDown = upsideDown;
+ this.FlipLeftToRight = leftToRight;
+ }
+
+ private void SetFlip(Av1TransformType transformType)
+ {
+ switch (transformType)
+ {
+ case Av1TransformType.DctDct:
+ case Av1TransformType.AdstDct:
+ case Av1TransformType.DctAdst:
+ case Av1TransformType.AdstAdst:
+ this.FlipUpsideDown = false;
+ this.FlipLeftToRight = false;
+ break;
+ case Av1TransformType.Identity:
+ case Av1TransformType.VerticalDct:
+ case Av1TransformType.HorizontalDct:
+ case Av1TransformType.VerticalAdst:
+ case Av1TransformType.HorizontalAdst:
+ this.FlipUpsideDown = false;
+ this.FlipLeftToRight = false;
+ break;
+ case Av1TransformType.FlipAdstDct:
+ case Av1TransformType.FlipAdstAdst:
+ case Av1TransformType.VerticalFlipAdst:
+ this.FlipUpsideDown = true;
+ this.FlipLeftToRight = false;
+ break;
+ case Av1TransformType.DctFlipAdst:
+ case Av1TransformType.AdstFlipAdst:
+ case Av1TransformType.HorizontalFlipAdst:
+ this.FlipUpsideDown = false;
+ this.FlipLeftToRight = true;
+ break;
+ case Av1TransformType.FlipAdstFlipAdst:
+ this.FlipUpsideDown = true;
+ this.FlipLeftToRight = true;
+ break;
+ default:
+ Guard.IsTrue(false, nameof(transformType), "Unknown transform type for determining flip.");
+ break;
+ }
+ }
+
+ ///
+ /// SVT: set_fwd_txfm_non_scale_range
+ ///
+ private void NonScaleRange()
+ {
+ Span range_mult2_col = RangeMulti2List[(int)this.TransformFunctionTypeColumn];
+ if (this.TransformFunctionTypeColumn != Av1TransformFunctionType.Invalid)
+ {
+ int stage_num_col = this.StageNumberColumn;
+ for (int i = 0; i < stage_num_col; ++i)
+ {
+ this.StageRangeColumn[i] = (byte)((range_mult2_col[i] + 1) >> 1);
+ }
+ }
+
+ if (this.TransformFunctionTypeRow != Av1TransformFunctionType.Invalid)
+ {
+ int stage_num_row = this.StageNumberRow;
+ Span range_mult2_row = RangeMulti2List[(int)this.TransformFunctionTypeRow];
+ for (int i = 0; i < stage_num_row; ++i)
+ {
+ this.StageRangeRow[i] = (byte)((range_mult2_col[this.StageNumberColumn - 1] + range_mult2_row[i] + 1) >> 1);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformClass.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformClass.cs
new file mode 100644
index 0000000000..afc0354821
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformClass.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal enum Av1TransformClass
+{
+ Class2D = 0,
+ ClassHorizontal = 1,
+ ClassVertical = 2,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionParameters.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionParameters.cs
new file mode 100644
index 0000000000..ae24e659c3
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionParameters.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal class Av1TransformFunctionParameters
+{
+ public Av1TransformType TransformType { get; internal set; }
+
+ public Av1TransformSize TransformSize { get; internal set; }
+
+ public int EndOfBuffer { get; internal set; }
+
+ public bool IsLossless { get; internal set; }
+
+ public int BitDepth { get; internal set; }
+
+ public bool Is16BitPipeline { get; internal set; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionType.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionType.cs
new file mode 100644
index 0000000000..cd118454f0
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionType.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal enum Av1TransformFunctionType
+{
+ Dct4,
+ Dct8,
+ Dct16,
+ Dct32,
+ Dct64,
+ Adst4,
+ Adst8,
+ Adst16,
+ Adst32,
+ Identity4,
+ Identity8,
+ Identity16,
+ Identity32,
+ Identity64,
+ Invalid,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformMode.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformMode.cs
new file mode 100644
index 0000000000..a838dfc86b
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformMode.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal enum Av1TransformMode : byte
+{
+ Only4x4 = 0,
+ Largest = 1,
+ Select = 2,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSetType.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSetType.cs
new file mode 100644
index 0000000000..f25d1cbf5e
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSetType.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal enum Av1TransformSetType
+{
+ ///
+ /// DCT only.
+ ///
+ DctOnly,
+
+ ///
+ /// DCT + Identity only
+ ///
+ DctIdentity,
+
+ ///
+ /// Discrete Trig transforms w/o flip (4) + Identity (1)
+ ///
+ Dtt4Identity,
+
+ ///
+ /// Discrete Trig transforms w/o flip (4) + Identity (1) + 1D Hor/vert DCT (2)
+ ///
+ Dtt4Identity1dDct,
+
+ ///
+ /// Discrete Trig transforms w/ flip (9) + Identity (1) + 1D Hor/Ver DCT (2)
+ ///
+ Dtt9Identity1dDct,
+
+ ///
+ /// Discrete Trig transforms w/ flip (9) + Identity (1) + 1D Hor/Ver (6)
+ ///
+ All16
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs
new file mode 100644
index 0000000000..e812778335
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal enum Av1TransformSize : byte
+{
+ Size4x4,
+ Size8x8,
+ Size16x16,
+ Size32x32,
+ Size64x64,
+ Size4x8,
+ Size8x4,
+ Size8x16,
+ Size16x8,
+ Size16x32,
+ Size32x16,
+ Size32x64,
+ Size64x32,
+ Size4x16,
+ Size16x4,
+ Size8x32,
+ Size32x8,
+ Size16x64,
+ Size64x16,
+ AllSizes,
+ SquareSizes = Size4x8,
+ Invalid = 255,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs
new file mode 100644
index 0000000000..3341d2fc80
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs
@@ -0,0 +1,214 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal static class Av1TransformSizeExtensions
+{
+ private static readonly int[] Size2d = [
+ 16, 64, 256, 1024, 4096, 32, 32, 128, 128, 512, 512, 2048, 2048, 64, 64, 256, 256, 1024, 1024];
+
+ private static readonly Av1TransformSize[] SubTransformSize = [
+ Av1TransformSize.Size4x4, // TX_4X4
+ Av1TransformSize.Size4x4, // TX_8X8
+ Av1TransformSize.Size8x8, // TX_16X16
+ Av1TransformSize.Size16x16, // TX_32X32
+ Av1TransformSize.Size32x32, // TX_64X64
+ Av1TransformSize.Size4x4, // TX_4X8
+ Av1TransformSize.Size4x4, // TX_8X4
+ Av1TransformSize.Size8x8, // TX_8X16
+ Av1TransformSize.Size8x8, // TX_16X8
+ Av1TransformSize.Size16x16, // TX_16X32
+ Av1TransformSize.Size16x16, // TX_32X16
+ Av1TransformSize.Size32x32, // TX_32X64
+ Av1TransformSize.Size32x32, // TX_64X32
+ Av1TransformSize.Size4x8, // TX_4X16
+ Av1TransformSize.Size8x4, // TX_16X4
+ Av1TransformSize.Size8x16, // TX_8X32
+ Av1TransformSize.Size16x8, // TX_32X8
+ Av1TransformSize.Size16x32, // TX_16X64
+ Av1TransformSize.Size32x16, // TX_64X16
+ ];
+
+ // Transform block width in units.
+ private static readonly int[] WideUnit = [1, 2, 4, 8, 16, 1, 2, 2, 4, 4, 8, 8, 16, 1, 4, 2, 8, 4, 16];
+
+ // Transform block height in unit
+ private static readonly int[] HighUnit = [1, 2, 4, 8, 16, 2, 1, 4, 2, 8, 4, 16, 8, 4, 1, 8, 2, 16, 4];
+
+ // Transform size conversion into Block Size
+ private static readonly Av1BlockSize[] BlockSize = [
+ Av1BlockSize.Block4x4, // TX_4X4
+ Av1BlockSize.Block8x8, // TX_8X8
+ Av1BlockSize.Block16x16, // TX_16X16
+ Av1BlockSize.Block32x32, // TX_32X32
+ Av1BlockSize.Block64x64, // TX_64X64
+ Av1BlockSize.Block4x8, // TX_4X8
+ Av1BlockSize.Block8x4, // TX_8X4
+ Av1BlockSize.Block8x16, // TX_8X16
+ Av1BlockSize.Block16x8, // TX_16X8
+ Av1BlockSize.Block16x32, // TX_16X32
+ Av1BlockSize.Block32x16, // TX_32X16
+ Av1BlockSize.Block32x64, // TX_32X64
+ Av1BlockSize.Block64x32, // TX_64X32
+ Av1BlockSize.Block4x16, // TX_4X16
+ Av1BlockSize.Block16x4, // TX_16X4
+ Av1BlockSize.Block8x32, // TX_8X32
+ Av1BlockSize.Block32x8, // TX_32X8
+ Av1BlockSize.Block16x64, // TX_16X64
+ Av1BlockSize.Block64x16, // TX_64X16
+ ];
+
+ private static readonly Av1TransformSize[] SquareMap = [
+ Av1TransformSize.Size4x4, // TX_4X4
+ Av1TransformSize.Size8x8, // TX_8X8
+ Av1TransformSize.Size16x16, // TX_16X16
+ Av1TransformSize.Size32x32, // TX_32X32
+ Av1TransformSize.Size64x64, // TX_64X64
+ Av1TransformSize.Size4x4, // TX_4X8
+ Av1TransformSize.Size4x4, // TX_8X4
+ Av1TransformSize.Size8x8, // TX_8X16
+ Av1TransformSize.Size8x8, // TX_16X8
+ Av1TransformSize.Size16x16, // TX_16X32
+ Av1TransformSize.Size16x16, // TX_32X16
+ Av1TransformSize.Size32x32, // TX_32X64
+ Av1TransformSize.Size32x32, // TX_64X32
+ Av1TransformSize.Size4x4, // TX_4X16
+ Av1TransformSize.Size4x4, // TX_16X4
+ Av1TransformSize.Size8x8, // TX_8X32
+ Av1TransformSize.Size8x8, // TX_32X8
+ Av1TransformSize.Size16x16, // TX_16X64
+ Av1TransformSize.Size16x16, // TX_64X16
+ ];
+
+ private static readonly Av1TransformSize[] SquareUpMap = [
+ Av1TransformSize.Size4x4, // TX_4X4
+ Av1TransformSize.Size8x8, // TX_8X8
+ Av1TransformSize.Size16x16, // TX_16X16
+ Av1TransformSize.Size32x32, // TX_32X32
+ Av1TransformSize.Size64x64, // TX_64X64
+ Av1TransformSize.Size8x8, // TX_4X8
+ Av1TransformSize.Size8x8, // TX_8X4
+ Av1TransformSize.Size16x16, // TX_8X16
+ Av1TransformSize.Size16x16, // TX_16X8
+ Av1TransformSize.Size32x32, // TX_16X32
+ Av1TransformSize.Size32x32, // TX_32X16
+ Av1TransformSize.Size64x64, // TX_32X64
+ Av1TransformSize.Size64x64, // TX_64X32
+ Av1TransformSize.Size16x16, // TX_4X16
+ Av1TransformSize.Size16x16, // TX_16X4
+ Av1TransformSize.Size32x32, // TX_8X32
+ Av1TransformSize.Size32x32, // TX_32X8
+ Av1TransformSize.Size64x64, // TX_16X64
+ Av1TransformSize.Size64x64, // TX_64X16
+ ];
+
+ private static readonly int[] Log2Minus4 = [
+ 0, // TX_4X4
+ 2, // TX_8X8
+ 4, // TX_16X16
+ 6, // TX_32X32
+ 6, // TX_64X64
+ 1, // TX_4X8
+ 1, // TX_8X4
+ 3, // TX_8X16
+ 3, // TX_16X8
+ 5, // TX_16X32
+ 5, // TX_32X16
+ 6, // TX_32X64
+ 6, // TX_64X32
+ 2, // TX_4X16
+ 2, // TX_16X4
+ 4, // TX_8X32
+ 4, // TX_32X8
+ 5, // TX_16X64
+ 5, // TX_64X16
+ ];
+
+ // Transform block width in log2
+ private static readonly int[] BlockWidthLog2 = [
+ 2, 3, 4, 5, 6, 2, 3, 3, 4, 4, 5, 5, 6, 2, 4, 3, 5, 4, 6,
+ ];
+
+ // Transform block height in log2
+ private static readonly int[] BlockHeightLog2 = [
+ 2, 3, 4, 5, 6, 3, 2, 4, 3, 5, 4, 6, 5, 4, 2, 5, 3, 6, 4,
+ ];
+
+ public static int GetSize2d(this Av1TransformSize size) => Size2d[(int)size];
+
+ public static int GetScale(this Av1TransformSize size)
+ {
+ int pels = Size2d[(int)size];
+ return (pels > 1024) ? 2 : (pels > 256) ? 1 : 0;
+ }
+
+ public static int GetWidth(this Av1TransformSize size) => WideUnit[(int)size] << 2;
+
+ public static int GetHeight(this Av1TransformSize size) => HighUnit[(int)size] << 2;
+
+ public static int Get4x4WideCount(this Av1TransformSize size) => WideUnit[(int)size];
+
+ public static int Get4x4HighCount(this Av1TransformSize size) => HighUnit[(int)size];
+
+ public static Av1TransformSize GetSubSize(this Av1TransformSize size) => SubTransformSize[(int)size];
+
+ public static Av1TransformSize GetSquareSize(this Av1TransformSize size) => SquareMap[(int)size];
+
+ public static Av1TransformSize GetSquareUpSize(this Av1TransformSize size) => SquareUpMap[(int)size];
+
+ public static Av1BlockSize ToBlockSize(this Av1TransformSize transformSize) => BlockSize[(int)transformSize];
+
+ public static int GetLog2Minus4(this Av1TransformSize size) => Log2Minus4[(int)size];
+
+ public static Av1TransformSize GetAdjusted(this Av1TransformSize size) => size switch
+ {
+ Av1TransformSize.Size64x64 or Av1TransformSize.Size64x32 or Av1TransformSize.Size32x64 => Av1TransformSize.Size32x32,
+ Av1TransformSize.Size64x16 => Av1TransformSize.Size32x16,
+ Av1TransformSize.Size16x64 => Av1TransformSize.Size16x32,
+ _ => size
+ };
+
+ public static int GetBlockWidthLog2(this Av1TransformSize size) => BlockWidthLog2[(int)GetAdjusted(size)];
+
+ public static int GetBlockHeightLog2(this Av1TransformSize size) => BlockHeightLog2[(int)GetAdjusted(size)];
+
+ public static int GetRectangleLogRatio(this Av1TransformSize size)
+ {
+ int col = GetWidth(size);
+ int row = GetHeight(size);
+ if (col == row)
+ {
+ return 0;
+ }
+
+ if (col > row)
+ {
+ if (col == row * 2)
+ {
+ return 1;
+ }
+
+ if (col == row * 4)
+ {
+ return 2;
+ }
+
+ throw new InvalidImageContentException("Unsupported transform size");
+ }
+ else
+ {
+ if (row == col * 2)
+ {
+ return -1;
+ }
+
+ if (row == col * 4)
+ {
+ return -2;
+ }
+
+ throw new InvalidImageContentException("Unsupported transform size");
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs
new file mode 100644
index 0000000000..96867d2ab1
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal enum Av1TransformType : byte
+{
+ ///
+ /// DCT in both horizontal and vertical.
+ ///
+ DctDct,
+
+ ///
+ /// ADST in vertical, DCT in horizontal.
+ ///
+ AdstDct,
+
+ ///
+ /// DCT in vertical, ADST in horizontal.
+ ///
+ DctAdst,
+
+ ///
+ /// ADST in both directions.
+ ///
+ AdstAdst,
+ FlipAdstDct,
+ DctFlipAdst,
+ FlipAdstFlipAdst,
+ AdstFlipAdst,
+ FlipAdstAdst,
+ Identity,
+ VerticalDct,
+ HorizontalDct,
+ VerticalAdst,
+ HorizontalAdst,
+ VerticalFlipAdst,
+ HorizontalFlipAdst,
+
+ ///
+ /// Number of Transform types.
+ ///
+ AllTransformTypes,
+
+ ///
+ /// Invalid value.
+ ///
+ Invalid,
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType1d.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType1d.cs
new file mode 100644
index 0000000000..a201493198
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType1d.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal enum Av1TransformType1d
+{
+ Dct,
+ Adst,
+ FlipAdst,
+ Identity
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformTypeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformTypeExtensions.cs
new file mode 100644
index 0000000000..c03b3b4406
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformTypeExtensions.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+internal static class Av1TransformTypeExtensions
+{
+ private static readonly Av1TransformClass[] Type2Class = [
+ Av1TransformClass.Class2D, // DCT_DCT
+ Av1TransformClass.Class2D, // ADST_DCT
+ Av1TransformClass.Class2D, // DCT_ADST
+ Av1TransformClass.Class2D, // ADST_ADST
+ Av1TransformClass.Class2D, // FLIPADST_DCT
+ Av1TransformClass.Class2D, // DCT_FLIPADST
+ Av1TransformClass.Class2D, // FLIPADST_FLIPADST
+ Av1TransformClass.Class2D, // ADST_FLIPADST
+ Av1TransformClass.Class2D, // FLIPADST_ADST
+ Av1TransformClass.Class2D, // IDTX
+ Av1TransformClass.ClassVertical, // V_DCT
+ Av1TransformClass.ClassHorizontal, // H_DCT
+ Av1TransformClass.ClassVertical, // V_ADST
+ Av1TransformClass.ClassHorizontal, // H_ADST
+ Av1TransformClass.ClassVertical, // V_FLIPADST
+ Av1TransformClass.ClassHorizontal, // H_FLIPADST
+ ];
+
+ private static readonly bool[][] ExtendedTransformUsed = [
+ [true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
+ [true, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false],
+ [true, true, true, true, false, false, false, false, false, true, false, false, false, false, false, false],
+ [true, true, true, true, false, false, false, false, false, true, true, true, false, false, false, false],
+ [true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false],
+ [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true],
+ ];
+
+ public static Av1TransformClass ToClass(this Av1TransformType transformType) => Type2Class[(int)transformType];
+
+ public static bool IsExtendedSetUsed(this Av1TransformType transformType, Av1TransformSetType setType)
+ => ExtendedTransformUsed[(int)setType][(int)transformType];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs
new file mode 100644
index 0000000000..927e333e83
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs
@@ -0,0 +1,188 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+
+internal class Av1Adst16Forward1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 16, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 16, nameof(output));
+ TransformScalar(ref input[0], ref output[0], cosBit);
+ }
+
+ private static void TransformScalar(ref int input, ref int output, int cosBit)
+ {
+ Span temp0 = stackalloc int[16];
+ Span temp1 = stackalloc int[16];
+
+ // stage 0;
+
+ // stage 1;
+ Guard.IsFalse(output == input, nameof(output), "Cannot operate on same buffer for input and output.");
+ temp1[0] = input;
+ temp1[1] = -Unsafe.Add(ref input, 15);
+ temp1[2] = -Unsafe.Add(ref input, 7);
+ temp1[3] = Unsafe.Add(ref input, 8);
+ temp1[4] = -Unsafe.Add(ref input, 3);
+ temp1[5] = Unsafe.Add(ref input, 12);
+ temp1[6] = Unsafe.Add(ref input, 4);
+ temp1[7] = -Unsafe.Add(ref input, 11);
+ temp1[8] = -Unsafe.Add(ref input, 1);
+ temp1[9] = Unsafe.Add(ref input, 14);
+ temp1[10] = Unsafe.Add(ref input, 6);
+ temp1[11] = -Unsafe.Add(ref input, 9);
+ temp1[12] = Unsafe.Add(ref input, 2);
+ temp1[13] = -Unsafe.Add(ref input, 13);
+ temp1[14] = -Unsafe.Add(ref input, 5);
+ temp1[15] = Unsafe.Add(ref input, 10);
+
+ // stage 2
+ Span cospi = Av1SinusConstants.CosinusPi(cosBit);
+ temp0[0] = temp1[0];
+ temp0[1] = temp1[1];
+ temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[2], cospi[32], temp1[3], cosBit);
+ temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[2], -cospi[32], temp1[3], cosBit);
+ temp0[4] = temp1[4];
+ temp0[5] = temp1[5];
+ temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[6], cospi[32], temp1[7], cosBit);
+ temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[6], -cospi[32], temp1[7], cosBit);
+ temp0[8] = temp1[8];
+ temp0[9] = temp1[9];
+ temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[10], cospi[32], temp1[11], cosBit);
+ temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[10], -cospi[32], temp1[11], cosBit);
+ temp0[12] = temp1[12];
+ temp0[13] = temp1[13];
+ temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[14], cospi[32], temp1[15], cosBit);
+ temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[14], -cospi[32], temp1[15], cosBit);
+
+ // stage 3
+ temp1[0] = temp0[0] + temp0[2];
+ temp1[1] = temp0[1] + temp0[3];
+ temp1[2] = temp0[0] - temp0[2];
+ temp1[3] = temp0[1] - temp0[3];
+ temp1[4] = temp0[4] + temp0[6];
+ temp1[5] = temp0[5] + temp0[7];
+ temp1[6] = temp0[4] - temp0[6];
+ temp1[7] = temp0[5] - temp0[7];
+ temp1[8] = temp0[8] + temp0[10];
+ temp1[9] = temp0[9] + temp0[11];
+ temp1[10] = temp0[8] - temp0[10];
+ temp1[11] = temp0[9] - temp0[11];
+ temp1[12] = temp0[12] + temp0[14];
+ temp1[13] = temp0[13] + temp0[15];
+ temp1[14] = temp0[12] - temp0[14];
+ temp1[15] = temp0[13] - temp0[15];
+
+ // stage 4
+ temp0[0] = temp1[0];
+ temp0[1] = temp1[1];
+ temp0[2] = temp1[2];
+ temp0[3] = temp1[3];
+ temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[4], cospi[48], temp1[5], cosBit);
+ temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[4], -cospi[16], temp1[5], cosBit);
+ temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[6], cospi[16], temp1[7], cosBit);
+ temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[6], cospi[48], temp1[7], cosBit);
+ temp0[8] = temp1[8];
+ temp0[9] = temp1[9];
+ temp0[10] = temp1[10];
+ temp0[11] = temp1[11];
+ temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[12], cospi[48], temp1[13], cosBit);
+ temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[12], -cospi[16], temp1[13], cosBit);
+ temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[14], cospi[16], temp1[15], cosBit);
+ temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[14], cospi[48], temp1[15], cosBit);
+
+ // stage 5
+ temp1[0] = temp0[0] + temp0[4];
+ temp1[1] = temp0[1] + temp0[5];
+ temp1[2] = temp0[2] + temp0[6];
+ temp1[3] = temp0[3] + temp0[7];
+ temp1[4] = temp0[0] - temp0[4];
+ temp1[5] = temp0[1] - temp0[5];
+ temp1[6] = temp0[2] - temp0[6];
+ temp1[7] = temp0[3] - temp0[7];
+ temp1[8] = temp0[8] + temp0[12];
+ temp1[9] = temp0[9] + temp0[13];
+ temp1[10] = temp0[10] + temp0[14];
+ temp1[11] = temp0[11] + temp0[15];
+ temp1[12] = temp0[8] - temp0[12];
+ temp1[13] = temp0[9] - temp0[13];
+ temp1[14] = temp0[10] - temp0[14];
+ temp1[15] = temp0[11] - temp0[15];
+
+ // stage 6
+ temp0[0] = temp1[0];
+ temp0[1] = temp1[1];
+ temp0[2] = temp1[2];
+ temp0[3] = temp1[3];
+ temp0[4] = temp1[4];
+ temp0[5] = temp1[5];
+ temp0[6] = temp1[6];
+ temp0[7] = temp1[7];
+ temp0[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp1[8], cospi[56], temp1[9], cosBit);
+ temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[8], -cospi[8], temp1[9], cosBit);
+ temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp1[10], cospi[24], temp1[11], cosBit);
+ temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[10], -cospi[40], temp1[11], cosBit);
+ temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp1[12], cospi[8], temp1[13], cosBit);
+ temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp1[12], cospi[56], temp1[13], cosBit);
+ temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp1[14], cospi[40], temp1[15], cosBit);
+ temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp1[14], cospi[24], temp1[15], cosBit);
+
+ // stage 7
+ temp1[0] = temp0[0] + temp0[8];
+ temp1[1] = temp0[1] + temp0[9];
+ temp1[2] = temp0[2] + temp0[10];
+ temp1[3] = temp0[3] + temp0[11];
+ temp1[4] = temp0[4] + temp0[12];
+ temp1[5] = temp0[5] + temp0[13];
+ temp1[6] = temp0[6] + temp0[14];
+ temp1[7] = temp0[7] + temp0[15];
+ temp1[8] = temp0[0] - temp0[8];
+ temp1[9] = temp0[1] - temp0[9];
+ temp1[10] = temp0[2] - temp0[10];
+ temp1[11] = temp0[3] - temp0[11];
+ temp1[12] = temp0[4] - temp0[12];
+ temp1[13] = temp0[5] - temp0[13];
+ temp1[14] = temp0[6] - temp0[14];
+ temp1[15] = temp0[7] - temp0[15];
+
+ // stage 8
+ temp0[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[2], temp1[0], cospi[62], temp1[1], cosBit);
+ temp0[1] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[62], temp1[0], -cospi[2], temp1[1], cosBit);
+ temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[10], temp1[2], cospi[54], temp1[3], cosBit);
+ temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[54], temp1[2], -cospi[10], temp1[3], cosBit);
+ temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[18], temp1[4], cospi[46], temp1[5], cosBit);
+ temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[46], temp1[4], -cospi[18], temp1[5], cosBit);
+ temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[26], temp1[6], cospi[38], temp1[7], cosBit);
+ temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[38], temp1[6], -cospi[26], temp1[7], cosBit);
+ temp0[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[34], temp1[8], cospi[30], temp1[9], cosBit);
+ temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[30], temp1[8], -cospi[34], temp1[9], cosBit);
+ temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[42], temp1[10], cospi[22], temp1[11], cosBit);
+ temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[22], temp1[10], -cospi[42], temp1[11], cosBit);
+ temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[50], temp1[12], cospi[14], temp1[13], cosBit);
+ temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[14], temp1[12], -cospi[50], temp1[13], cosBit);
+ temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[58], temp1[14], cospi[6], temp1[15], cosBit);
+ temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[6], temp1[14], -cospi[58], temp1[15], cosBit);
+
+ // stage 9
+ output = temp0[1];
+ Unsafe.Add(ref output, 1) = temp0[14];
+ Unsafe.Add(ref output, 2) = temp0[3];
+ Unsafe.Add(ref output, 3) = temp0[12];
+ Unsafe.Add(ref output, 4) = temp0[5];
+ Unsafe.Add(ref output, 5) = temp0[10];
+ Unsafe.Add(ref output, 6) = temp0[7];
+ Unsafe.Add(ref output, 7) = temp0[8];
+ Unsafe.Add(ref output, 8) = temp0[9];
+ Unsafe.Add(ref output, 9) = temp0[6];
+ Unsafe.Add(ref output, 10) = temp0[11];
+ Unsafe.Add(ref output, 11) = temp0[4];
+ Unsafe.Add(ref output, 12) = temp0[13];
+ Unsafe.Add(ref output, 13) = temp0[2];
+ Unsafe.Add(ref output, 14) = temp0[15];
+ Unsafe.Add(ref output, 15) = temp0[0];
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs
new file mode 100644
index 0000000000..ba907e3a04
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs
@@ -0,0 +1,399 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+
+internal class Av1Adst32Forward1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 32, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 32, nameof(output));
+ TransformScalar(ref input[0], ref output[0], cosBit);
+ }
+
+ private static void TransformScalar(ref int input, ref int outputRef, int cosBit)
+ {
+ Span temp0 = stackalloc int[32];
+ Span temp1 = stackalloc int[32];
+
+ // stage 0;
+
+ // stage 1;
+ temp1[0] = Unsafe.Add(ref input, 31);
+ temp1[1] = input;
+ temp1[2] = Unsafe.Add(ref input, 29);
+ temp1[3] = Unsafe.Add(ref input, 2);
+ temp1[4] = Unsafe.Add(ref input, 27);
+ temp1[5] = Unsafe.Add(ref input, 4);
+ temp1[6] = Unsafe.Add(ref input, 25);
+ temp1[7] = Unsafe.Add(ref input, 6);
+ temp1[8] = Unsafe.Add(ref input, 23);
+ temp1[9] = Unsafe.Add(ref input, 8);
+ temp1[10] = Unsafe.Add(ref input, 21);
+ temp1[11] = Unsafe.Add(ref input, 10);
+ temp1[12] = Unsafe.Add(ref input, 19);
+ temp1[13] = Unsafe.Add(ref input, 12);
+ temp1[14] = Unsafe.Add(ref input, 17);
+ temp1[15] = Unsafe.Add(ref input, 14);
+ temp1[16] = Unsafe.Add(ref input, 15);
+ temp1[17] = Unsafe.Add(ref input, 16);
+ temp1[18] = Unsafe.Add(ref input, 13);
+ temp1[19] = Unsafe.Add(ref input, 18);
+ temp1[20] = Unsafe.Add(ref input, 11);
+ temp1[21] = Unsafe.Add(ref input, 20);
+ temp1[22] = Unsafe.Add(ref input, 9);
+ temp1[23] = Unsafe.Add(ref input, 22);
+ temp1[24] = Unsafe.Add(ref input, 7);
+ temp1[25] = Unsafe.Add(ref input, 24);
+ temp1[26] = Unsafe.Add(ref input, 5);
+ temp1[27] = Unsafe.Add(ref input, 26);
+ temp1[28] = Unsafe.Add(ref input, 3);
+ temp1[29] = Unsafe.Add(ref input, 28);
+ temp1[30] = Unsafe.Add(ref input, 1);
+ temp1[31] = Unsafe.Add(ref input, 30);
+
+ // stage 2
+ Span cospi = Av1SinusConstants.CosinusPi(cosBit);
+ temp0[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[1], temp1[0], cospi[63], temp1[1], cosBit);
+ temp0[1] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[1], temp1[1], cospi[63], temp1[0], cosBit);
+ temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[5], temp1[2], cospi[59], temp1[3], cosBit);
+ temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[5], temp1[3], cospi[59], temp1[2], cosBit);
+ temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[9], temp1[4], cospi[55], temp1[5], cosBit);
+ temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[9], temp1[5], cospi[55], temp1[4], cosBit);
+ temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[13], temp1[6], cospi[51], temp1[7], cosBit);
+ temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[13], temp1[7], cospi[51], temp1[6], cosBit);
+ temp0[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[17], temp1[8], cospi[47], temp1[9], cosBit);
+ temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[17], temp1[9], cospi[47], temp1[8], cosBit);
+ temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[21], temp1[10], cospi[43], temp1[11], cosBit);
+ temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[21], temp1[11], cospi[43], temp1[10], cosBit);
+ temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[25], temp1[12], cospi[39], temp1[13], cosBit);
+ temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[25], temp1[13], cospi[39], temp1[12], cosBit);
+ temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[29], temp1[14], cospi[35], temp1[15], cosBit);
+ temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[29], temp1[15], cospi[35], temp1[14], cosBit);
+ temp0[16] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[33], temp1[16], cospi[31], temp1[17], cosBit);
+ temp0[17] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[33], temp1[17], cospi[31], temp1[16], cosBit);
+ temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[37], temp1[18], cospi[27], temp1[19], cosBit);
+ temp0[19] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[37], temp1[19], cospi[27], temp1[18], cosBit);
+ temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[41], temp1[20], cospi[23], temp1[21], cosBit);
+ temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[41], temp1[21], cospi[23], temp1[20], cosBit);
+ temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[45], temp1[22], cospi[19], temp1[23], cosBit);
+ temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[45], temp1[23], cospi[19], temp1[22], cosBit);
+ temp0[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[49], temp1[24], cospi[15], temp1[25], cosBit);
+ temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[49], temp1[25], cospi[15], temp1[24], cosBit);
+ temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[53], temp1[26], cospi[11], temp1[27], cosBit);
+ temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[53], temp1[27], cospi[11], temp1[26], cosBit);
+ temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[57], temp1[28], cospi[7], temp1[29], cosBit);
+ temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[57], temp1[29], cospi[7], temp1[28], cosBit);
+ temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[61], temp1[30], cospi[3], temp1[31], cosBit);
+ temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[61], temp1[31], cospi[3], temp1[30], cosBit);
+
+ // stage 3
+ temp1[0] = temp0[0] + temp0[16];
+ temp1[1] = temp0[1] + temp0[17];
+ temp1[2] = temp0[2] + temp0[18];
+ temp1[3] = temp0[3] + temp0[19];
+ temp1[4] = temp0[4] + temp0[20];
+ temp1[5] = temp0[5] + temp0[21];
+ temp1[6] = temp0[6] + temp0[22];
+ temp1[7] = temp0[7] + temp0[23];
+ temp1[8] = temp0[8] + temp0[24];
+ temp1[9] = temp0[9] + temp0[25];
+ temp1[10] = temp0[10] + temp0[26];
+ temp1[11] = temp0[11] + temp0[27];
+ temp1[12] = temp0[12] + temp0[28];
+ temp1[13] = temp0[13] + temp0[29];
+ temp1[14] = temp0[14] + temp0[30];
+ temp1[15] = temp0[15] + temp0[31];
+ temp1[16] = -temp0[16] + temp0[0];
+ temp1[17] = -temp0[17] + temp0[1];
+ temp1[18] = -temp0[18] + temp0[2];
+ temp1[19] = -temp0[19] + temp0[3];
+ temp1[20] = -temp0[20] + temp0[4];
+ temp1[21] = -temp0[21] + temp0[5];
+ temp1[22] = -temp0[22] + temp0[6];
+ temp1[23] = -temp0[23] + temp0[7];
+ temp1[24] = -temp0[24] + temp0[8];
+ temp1[25] = -temp0[25] + temp0[9];
+ temp1[26] = -temp0[26] + temp0[10];
+ temp1[27] = -temp0[27] + temp0[11];
+ temp1[28] = -temp0[28] + temp0[12];
+ temp1[29] = -temp0[29] + temp0[13];
+ temp1[30] = -temp0[30] + temp0[14];
+ temp1[31] = -temp0[31] + temp0[15];
+
+ // stage 4
+ temp0[0] = temp1[0];
+ temp0[1] = temp1[1];
+ temp0[2] = temp1[2];
+ temp0[3] = temp1[3];
+ temp0[4] = temp1[4];
+ temp0[5] = temp1[5];
+ temp0[6] = temp1[6];
+ temp0[7] = temp1[7];
+ temp0[8] = temp1[8];
+ temp0[9] = temp1[9];
+ temp0[10] = temp1[10];
+ temp0[11] = temp1[11];
+ temp0[12] = temp1[12];
+ temp0[13] = temp1[13];
+ temp0[14] = temp1[14];
+ temp0[15] = temp1[15];
+ temp0[16] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[4], temp1[16], cospi[60], temp1[17], cosBit);
+ temp0[17] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[4], temp1[17], cospi[60], temp1[16], cosBit);
+ temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[20], temp1[18], cospi[44], temp1[19], cosBit);
+ temp0[19] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[20], temp1[19], cospi[44], temp1[18], cosBit);
+ temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[36], temp1[20], cospi[28], temp1[21], cosBit);
+ temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[36], temp1[21], cospi[28], temp1[20], cosBit);
+ temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[52], temp1[22], cospi[12], temp1[23], cosBit);
+ temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[52], temp1[23], cospi[12], temp1[22], cosBit);
+ temp0[24] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[60], temp1[24], cospi[4], temp1[25], cosBit);
+ temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp1[25], cospi[4], temp1[24], cosBit);
+ temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[44], temp1[26], cospi[20], temp1[27], cosBit);
+ temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp1[27], cospi[20], temp1[26], cosBit);
+ temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[28], temp1[28], cospi[36], temp1[29], cosBit);
+ temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp1[29], cospi[36], temp1[28], cosBit);
+ temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[12], temp1[30], cospi[52], temp1[31], cosBit);
+ temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp1[31], cospi[52], temp1[30], cosBit);
+
+ // stage 5
+ temp1[0] = temp0[0] + temp0[8];
+ temp1[1] = temp0[1] + temp0[9];
+ temp1[2] = temp0[2] + temp0[10];
+ temp1[3] = temp0[3] + temp0[11];
+ temp1[4] = temp0[4] + temp0[12];
+ temp1[5] = temp0[5] + temp0[13];
+ temp1[6] = temp0[6] + temp0[14];
+ temp1[7] = temp0[7] + temp0[15];
+ temp1[8] = -temp0[8] + temp0[0];
+ temp1[9] = -temp0[9] + temp0[1];
+ temp1[10] = -temp0[10] + temp0[2];
+ temp1[11] = -temp0[11] + temp0[3];
+ temp1[12] = -temp0[12] + temp0[4];
+ temp1[13] = -temp0[13] + temp0[5];
+ temp1[14] = -temp0[14] + temp0[6];
+ temp1[15] = -temp0[15] + temp0[7];
+ temp1[16] = temp0[16] + temp0[24];
+ temp1[17] = temp0[17] + temp0[25];
+ temp1[18] = temp0[18] + temp0[26];
+ temp1[19] = temp0[19] + temp0[27];
+ temp1[20] = temp0[20] + temp0[28];
+ temp1[21] = temp0[21] + temp0[29];
+ temp1[22] = temp0[22] + temp0[30];
+ temp1[23] = temp0[23] + temp0[31];
+ temp1[24] = -temp0[24] + temp0[16];
+ temp1[25] = -temp0[25] + temp0[17];
+ temp1[26] = -temp0[26] + temp0[18];
+ temp1[27] = -temp0[27] + temp0[19];
+ temp1[28] = -temp0[28] + temp0[20];
+ temp1[29] = -temp0[29] + temp0[21];
+ temp1[30] = -temp0[30] + temp0[22];
+ temp1[31] = -temp0[31] + temp0[23];
+
+ // stage 6
+ temp0[0] = temp1[0];
+ temp0[1] = temp1[1];
+ temp0[2] = temp1[2];
+ temp0[3] = temp1[3];
+ temp0[4] = temp1[4];
+ temp0[5] = temp1[5];
+ temp0[6] = temp1[6];
+ temp0[7] = temp1[7];
+ temp0[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp1[8], cospi[56], temp1[9], cosBit);
+ temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp1[9], cospi[56], temp1[8], cosBit);
+ temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp1[10], cospi[24], temp1[11], cosBit);
+ temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp1[11], cospi[24], temp1[10], cosBit);
+ temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp1[12], cospi[8], temp1[13], cosBit);
+ temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[13], cospi[8], temp1[12], cosBit);
+ temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp1[14], cospi[40], temp1[15], cosBit);
+ temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[15], cospi[40], temp1[14], cosBit);
+ temp0[16] = temp1[16];
+ temp0[17] = temp1[17];
+ temp0[18] = temp1[18];
+ temp0[19] = temp1[19];
+ temp0[20] = temp1[20];
+ temp0[21] = temp1[21];
+ temp0[22] = temp1[22];
+ temp0[23] = temp1[23];
+ temp0[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp1[24], cospi[56], temp1[25], cosBit);
+ temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp1[25], cospi[56], temp1[24], cosBit);
+ temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp1[26], cospi[24], temp1[27], cosBit);
+ temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp1[27], cospi[24], temp1[26], cosBit);
+ temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp1[28], cospi[8], temp1[29], cosBit);
+ temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[29], cospi[8], temp1[28], cosBit);
+ temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp1[30], cospi[40], temp1[31], cosBit);
+ temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[31], cospi[40], temp1[30], cosBit);
+
+ // stage 7
+ temp1[0] = temp0[0] + temp0[4];
+ temp1[1] = temp0[1] + temp0[5];
+ temp1[2] = temp0[2] + temp0[6];
+ temp1[3] = temp0[3] + temp0[7];
+ temp1[4] = -temp0[4] + temp0[0];
+ temp1[5] = -temp0[5] + temp0[1];
+ temp1[6] = -temp0[6] + temp0[2];
+ temp1[7] = -temp0[7] + temp0[3];
+ temp1[8] = temp0[8] + temp0[12];
+ temp1[9] = temp0[9] + temp0[13];
+ temp1[10] = temp0[10] + temp0[14];
+ temp1[11] = temp0[11] + temp0[15];
+ temp1[12] = -temp0[12] + temp0[8];
+ temp1[13] = -temp0[13] + temp0[9];
+ temp1[14] = -temp0[14] + temp0[10];
+ temp1[15] = -temp0[15] + temp0[11];
+ temp1[16] = temp0[16] + temp0[20];
+ temp1[17] = temp0[17] + temp0[21];
+ temp1[18] = temp0[18] + temp0[22];
+ temp1[19] = temp0[19] + temp0[23];
+ temp1[20] = -temp0[20] + temp0[16];
+ temp1[21] = -temp0[21] + temp0[17];
+ temp1[22] = -temp0[22] + temp0[18];
+ temp1[23] = -temp0[23] + temp0[19];
+ temp1[24] = temp0[24] + temp0[28];
+ temp1[25] = temp0[25] + temp0[29];
+ temp1[26] = temp0[26] + temp0[30];
+ temp1[27] = temp0[27] + temp0[31];
+ temp1[28] = -temp0[28] + temp0[24];
+ temp1[29] = -temp0[29] + temp0[25];
+ temp1[30] = -temp0[30] + temp0[26];
+ temp1[31] = -temp0[31] + temp0[27];
+
+ // stage 8
+ temp0[0] = temp1[0];
+ temp0[1] = temp1[1];
+ temp0[2] = temp1[2];
+ temp0[3] = temp1[3];
+ temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[4], cospi[48], temp1[5], cosBit);
+ temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[5], cospi[48], temp1[4], cosBit);
+ temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[6], cospi[16], temp1[7], cosBit);
+ temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[7], cospi[16], temp1[6], cosBit);
+ temp0[8] = temp1[8];
+ temp0[9] = temp1[9];
+ temp0[10] = temp1[10];
+ temp0[11] = temp1[11];
+ temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[12], cospi[48], temp1[13], cosBit);
+ temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[13], cospi[48], temp1[12], cosBit);
+ temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[14], cospi[16], temp1[15], cosBit);
+ temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[15], cospi[16], temp1[14], cosBit);
+ temp0[16] = temp1[16];
+ temp0[17] = temp1[17];
+ temp0[18] = temp1[18];
+ temp0[19] = temp1[19];
+ temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[20], cospi[48], temp1[21], cosBit);
+ temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[21], cospi[48], temp1[20], cosBit);
+ temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[22], cospi[16], temp1[23], cosBit);
+ temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[23], cospi[16], temp1[22], cosBit);
+ temp0[24] = temp1[24];
+ temp0[25] = temp1[25];
+ temp0[26] = temp1[26];
+ temp0[27] = temp1[27];
+ temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[28], cospi[48], temp1[29], cosBit);
+ temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[29], cospi[48], temp1[28], cosBit);
+ temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[30], cospi[16], temp1[31], cosBit);
+ temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[31], cospi[16], temp1[30], cosBit);
+
+ // stage 9
+ temp1[0] = temp0[0] + temp0[2];
+ temp1[1] = temp0[1] + temp0[3];
+ temp1[2] = -temp0[2] + temp0[0];
+ temp1[3] = -temp0[3] + temp0[1];
+ temp1[4] = temp0[4] + temp0[6];
+ temp1[5] = temp0[5] + temp0[7];
+ temp1[6] = -temp0[6] + temp0[4];
+ temp1[7] = -temp0[7] + temp0[5];
+ temp1[8] = temp0[8] + temp0[10];
+ temp1[9] = temp0[9] + temp0[11];
+ temp1[10] = -temp0[10] + temp0[8];
+ temp1[11] = -temp0[11] + temp0[9];
+ temp1[12] = temp0[12] + temp0[14];
+ temp1[13] = temp0[13] + temp0[15];
+ temp1[14] = -temp0[14] + temp0[12];
+ temp1[15] = -temp0[15] + temp0[13];
+ temp1[16] = temp0[16] + temp0[18];
+ temp1[17] = temp0[17] + temp0[19];
+ temp1[18] = -temp0[18] + temp0[16];
+ temp1[19] = -temp0[19] + temp0[17];
+ temp1[20] = temp0[20] + temp0[22];
+ temp1[21] = temp0[21] + temp0[23];
+ temp1[22] = -temp0[22] + temp0[20];
+ temp1[23] = -temp0[23] + temp0[21];
+ temp1[24] = temp0[24] + temp0[26];
+ temp1[25] = temp0[25] + temp0[27];
+ temp1[26] = -temp0[26] + temp0[24];
+ temp1[27] = -temp0[27] + temp0[25];
+ temp1[28] = temp0[28] + temp0[30];
+ temp1[29] = temp0[29] + temp0[31];
+ temp1[30] = -temp0[30] + temp0[28];
+ temp1[31] = -temp0[31] + temp0[29];
+
+ // stage 10
+ temp0[0] = temp1[0];
+ temp0[1] = temp1[1];
+ temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[2], cospi[32], temp1[3], cosBit);
+ temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[3], cospi[32], temp1[2], cosBit);
+ temp0[4] = temp1[4];
+ temp0[5] = temp1[5];
+ temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[6], cospi[32], temp1[7], cosBit);
+ temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[7], cospi[32], temp1[6], cosBit);
+ temp0[8] = temp1[8];
+ temp0[9] = temp1[9];
+ temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[10], cospi[32], temp1[11], cosBit);
+ temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[11], cospi[32], temp1[10], cosBit);
+ temp0[12] = temp1[12];
+ temp0[13] = temp1[13];
+ temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[14], cospi[32], temp1[15], cosBit);
+ temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[15], cospi[32], temp1[14], cosBit);
+ temp0[16] = temp1[16];
+ temp0[17] = temp1[17];
+ temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[18], cospi[32], temp1[19], cosBit);
+ temp0[19] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[19], cospi[32], temp1[18], cosBit);
+ temp0[20] = temp1[20];
+ temp0[21] = temp1[21];
+ temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[22], cospi[32], temp1[23], cosBit);
+ temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[23], cospi[32], temp1[22], cosBit);
+ temp0[24] = temp1[24];
+ temp0[25] = temp1[25];
+ temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[26], cospi[32], temp1[27], cosBit);
+ temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[27], cospi[32], temp1[26], cosBit);
+ temp0[28] = temp1[28];
+ temp0[29] = temp1[29];
+ temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[30], cospi[32], temp1[31], cosBit);
+ temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[31], cospi[32], temp1[30], cosBit);
+
+ // stage 11
+ outputRef = temp0[0];
+ Unsafe.Add(ref outputRef, 1) = -temp0[16];
+ Unsafe.Add(ref outputRef, 2) = temp0[24];
+ Unsafe.Add(ref outputRef, 3) = -temp0[8];
+ Unsafe.Add(ref outputRef, 4) = temp0[12];
+ Unsafe.Add(ref outputRef, 5) = -temp0[28];
+ Unsafe.Add(ref outputRef, 6) = temp0[20];
+ Unsafe.Add(ref outputRef, 7) = -temp0[4];
+ Unsafe.Add(ref outputRef, 8) = temp0[6];
+ Unsafe.Add(ref outputRef, 9) = -temp0[22];
+ Unsafe.Add(ref outputRef, 10) = temp0[30];
+ Unsafe.Add(ref outputRef, 11) = -temp0[14];
+ Unsafe.Add(ref outputRef, 12) = temp0[10];
+ Unsafe.Add(ref outputRef, 13) = -temp0[26];
+ Unsafe.Add(ref outputRef, 14) = temp0[18];
+ Unsafe.Add(ref outputRef, 15) = -temp0[2];
+ Unsafe.Add(ref outputRef, 16) = temp0[3];
+ Unsafe.Add(ref outputRef, 17) = -temp0[19];
+ Unsafe.Add(ref outputRef, 18) = temp0[27];
+ Unsafe.Add(ref outputRef, 19) = -temp0[11];
+ Unsafe.Add(ref outputRef, 20) = temp0[15];
+ Unsafe.Add(ref outputRef, 21) = -temp0[31];
+ Unsafe.Add(ref outputRef, 22) = temp0[23];
+ Unsafe.Add(ref outputRef, 23) = -temp0[7];
+ Unsafe.Add(ref outputRef, 24) = temp0[5];
+ Unsafe.Add(ref outputRef, 25) = -temp0[21];
+ Unsafe.Add(ref outputRef, 26) = temp0[29];
+ Unsafe.Add(ref outputRef, 27) = -temp0[13];
+ Unsafe.Add(ref outputRef, 28) = temp0[9];
+ Unsafe.Add(ref outputRef, 29) = -temp0[25];
+ Unsafe.Add(ref outputRef, 30) = temp0[17];
+ Unsafe.Add(ref outputRef, 31) = -temp0[1];
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs
new file mode 100644
index 0000000000..38b11dfbfb
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+
+internal class Av1Adst4Forward1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 4, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 4, nameof(output));
+ TransformScalar(ref input[0], ref output[0], cosBit);
+ }
+
+ private static void TransformScalar(ref int input, ref int output, int cosBit)
+ {
+ Span sinpi = Av1SinusConstants.SinusPi(cosBit);
+ int x0, x1, x2, x3;
+ int s0, s1, s2, s3, s4, s5, s6, s7;
+
+ // stage 0
+ x0 = input;
+ x1 = Unsafe.Add(ref input, 1);
+ x2 = Unsafe.Add(ref input, 2);
+ x3 = Unsafe.Add(ref input, 3);
+
+ if (!(x0 != 0 | x1 != 0 | x2 != 0 | x3 != 0))
+ {
+ output = 0;
+ Unsafe.Add(ref output, 1) = 0;
+ Unsafe.Add(ref output, 2) = 0;
+ Unsafe.Add(ref output, 3) = 0;
+ return;
+ }
+
+ // stage 1
+ s0 = sinpi[1] * x0;
+ s1 = sinpi[4] * x0;
+ s2 = sinpi[2] * x1;
+ s3 = sinpi[1] * x1;
+ s4 = sinpi[3] * x2;
+ s5 = sinpi[4] * x3;
+ s6 = sinpi[2] * x3;
+ s7 = x0 + x1;
+
+ // stage 2
+ s7 -= x3;
+
+ // stage 3
+ x0 = s0 + s2;
+ x1 = sinpi[3] * s7;
+ x2 = s1 - s3;
+ x3 = s4;
+
+ // stage 4
+ x0 += s5;
+ x2 += s6;
+
+ // stage 5
+ s0 = x0 + x3;
+ s1 = x1;
+ s2 = x2 - x3;
+ s3 = x2 - x0;
+
+ // stage 6
+ s3 += x3;
+
+ // 1-D transform scaling factor is sqrt(2).
+ output = Av1Math.RoundShift(s0, cosBit);
+ Unsafe.Add(ref output, 1) = Av1Math.RoundShift(s1, cosBit);
+ Unsafe.Add(ref output, 2) = Av1Math.RoundShift(s2, cosBit);
+ Unsafe.Add(ref output, 3) = Av1Math.RoundShift(s3, cosBit);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs
new file mode 100644
index 0000000000..701973fc4c
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs
@@ -0,0 +1,96 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+
+internal class Av1Adst8Forward1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 8, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 8, nameof(output));
+ TransformScalar(ref input[0], ref output[0], cosBit);
+ }
+
+ private static void TransformScalar(ref int input, ref int output, int cosBit)
+ {
+ Span temp0 = stackalloc int[8];
+ Span temp1 = stackalloc int[8];
+
+ // stage 0;
+
+ // stage 1;
+ Guard.IsFalse(output == input, nameof(output), "Cannot operate on same buffer for input and output.");
+ temp0[0] = input;
+ temp0[1] = -Unsafe.Add(ref input, 7);
+ temp0[2] = -Unsafe.Add(ref input, 3);
+ temp0[3] = Unsafe.Add(ref input, 4);
+ temp0[4] = -Unsafe.Add(ref input, 1);
+ temp0[5] = Unsafe.Add(ref input, 6);
+ temp0[6] = Unsafe.Add(ref input, 2);
+ temp0[7] = -Unsafe.Add(ref input, 5);
+
+ // stage 2
+ Span cospi = Av1SinusConstants.CosinusPi(cosBit);
+ temp1[0] = temp0[0];
+ temp1[1] = temp0[1];
+ temp1[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[2], cospi[32], temp0[3], cosBit);
+ temp1[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[2], -cospi[32], temp0[3], cosBit);
+ temp1[4] = temp0[4];
+ temp1[5] = temp0[5];
+ temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[6], cospi[32], temp0[7], cosBit);
+ temp1[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[6], -cospi[32], temp0[7], cosBit);
+
+ // stage 3
+ temp0[0] = temp1[0] + temp1[2];
+ temp0[1] = temp1[1] + temp1[3];
+ temp0[2] = temp1[0] - temp1[2];
+ temp0[3] = temp1[1] - temp1[3];
+ temp0[4] = temp1[4] + temp1[6];
+ temp0[5] = temp1[5] + temp1[7];
+ temp0[6] = temp1[4] - temp1[6];
+ temp0[7] = temp1[5] - temp1[7];
+
+ // stage 4
+ temp1[0] = temp0[0];
+ temp1[1] = temp0[1];
+ temp1[2] = temp0[2];
+ temp1[3] = temp0[3];
+ temp1[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[4], cospi[48], temp0[5], cosBit);
+ temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[4], -cospi[16], temp0[5], cosBit);
+ temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[6], cospi[16], temp0[7], cosBit);
+ temp1[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[6], cospi[48], temp0[7], cosBit);
+
+ // stage 5
+ temp0[0] = temp1[0] + temp1[4];
+ temp0[1] = temp1[1] + temp1[5];
+ temp0[2] = temp1[2] + temp1[6];
+ temp0[3] = temp1[3] + temp1[7];
+ temp0[4] = temp1[0] - temp1[4];
+ temp0[5] = temp1[1] - temp1[5];
+ temp0[6] = temp1[2] - temp1[6];
+ temp0[7] = temp1[3] - temp1[7];
+
+ // stage 6
+ temp1[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[4], temp0[0], cospi[60], temp0[1], cosBit);
+ temp1[1] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[0], -cospi[4], temp0[1], cosBit);
+ temp1[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[20], temp0[2], cospi[44], temp0[3], cosBit);
+ temp1[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[2], -cospi[20], temp0[3], cosBit);
+ temp1[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[36], temp0[4], cospi[28], temp0[5], cosBit);
+ temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[4], -cospi[36], temp0[5], cosBit);
+ temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[52], temp0[6], cospi[12], temp0[7], cosBit);
+ temp1[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[6], -cospi[52], temp0[7], cosBit);
+
+ // stage 7
+ output = temp1[1];
+ Unsafe.Add(ref output, 1) = temp1[6];
+ Unsafe.Add(ref output, 2) = temp1[3];
+ Unsafe.Add(ref output, 3) = temp1[4];
+ Unsafe.Add(ref output, 4) = temp1[5];
+ Unsafe.Add(ref output, 5) = temp1[2];
+ Unsafe.Add(ref output, 6) = temp1[7];
+ Unsafe.Add(ref output, 7) = temp1[0];
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs
new file mode 100644
index 0000000000..ea515aad5d
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs
@@ -0,0 +1,151 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+
+internal class Av1Dct16Forward1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 16, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 16, nameof(output));
+ TransformScalar(ref input[0], ref output[0], cosBit);
+ }
+
+ private static void TransformScalar(ref int input, ref int output, int cosBit)
+ {
+ Span temp0 = stackalloc int[16];
+ Span temp1 = stackalloc int[16];
+
+ // stage 0;
+
+ // stage 1;
+ temp0[0] = Unsafe.Add(ref input, 0) + Unsafe.Add(ref input, 15);
+ temp0[1] = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 14);
+ temp0[2] = Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 13);
+ temp0[3] = Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 12);
+ temp0[4] = Unsafe.Add(ref input, 4) + Unsafe.Add(ref input, 11);
+ temp0[5] = Unsafe.Add(ref input, 5) + Unsafe.Add(ref input, 10);
+ temp0[6] = Unsafe.Add(ref input, 6) + Unsafe.Add(ref input, 9);
+ temp0[7] = Unsafe.Add(ref input, 7) + Unsafe.Add(ref input, 8);
+ temp0[8] = -Unsafe.Add(ref input, 8) + Unsafe.Add(ref input, 7);
+ temp0[9] = -Unsafe.Add(ref input, 9) + Unsafe.Add(ref input, 6);
+ temp0[10] = -Unsafe.Add(ref input, 10) + Unsafe.Add(ref input, 5);
+ temp0[11] = -Unsafe.Add(ref input, 11) + Unsafe.Add(ref input, 4);
+ temp0[12] = -Unsafe.Add(ref input, 12) + Unsafe.Add(ref input, 3);
+ temp0[13] = -Unsafe.Add(ref input, 13) + Unsafe.Add(ref input, 2);
+ temp0[14] = -Unsafe.Add(ref input, 14) + Unsafe.Add(ref input, 1);
+ temp0[15] = -Unsafe.Add(ref input, 15) + Unsafe.Add(ref input, 0);
+
+ // stage 2
+ Span cospi = Av1SinusConstants.CosinusPi(cosBit);
+ temp1[0] = temp0[0] + temp0[7];
+ temp1[1] = temp0[1] + temp0[6];
+ temp1[2] = temp0[2] + temp0[5];
+ temp1[3] = temp0[3] + temp0[4];
+ temp1[4] = -temp0[4] + temp0[3];
+ temp1[5] = -temp0[5] + temp0[2];
+ temp1[6] = -temp0[6] + temp0[1];
+ temp1[7] = -temp0[7] + temp0[0];
+ temp1[8] = temp0[8];
+ temp1[9] = temp0[9];
+ temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[10], cospi[32], temp0[13], cosBit);
+ temp1[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[11], cospi[32], temp0[12], cosBit);
+ temp1[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[12], cospi[32], temp0[11], cosBit);
+ temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[13], cospi[32], temp0[10], cosBit);
+ temp1[14] = temp0[14];
+ temp1[15] = temp0[15];
+
+ // stage 3
+ temp0[0] = temp1[0] + temp1[3];
+ temp0[1] = temp1[1] + temp1[2];
+ temp0[2] = -temp1[2] + temp1[1];
+ temp0[3] = -temp1[3] + temp1[0];
+ temp0[4] = temp1[4];
+ temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[5], cospi[32], temp1[6], cosBit);
+ temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[6], cospi[32], temp1[5], cosBit);
+ temp0[7] = temp1[7];
+ temp0[8] = temp1[8] + temp1[11];
+ temp0[9] = temp1[9] + temp1[10];
+ temp0[10] = -temp1[10] + temp1[9];
+ temp0[11] = -temp1[11] + temp1[8];
+ temp0[12] = -temp1[12] + temp1[15];
+ temp0[13] = -temp1[13] + temp1[14];
+ temp0[14] = temp1[14] + temp1[13];
+ temp0[15] = temp1[15] + temp1[12];
+
+ // stage 4
+ temp1[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[0], cospi[32], temp0[1], cosBit);
+ temp1[1] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[1], cospi[32], temp0[0], cosBit);
+ temp1[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[2], cospi[16], temp0[3], cosBit);
+ temp1[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[3], -cospi[16], temp0[2], cosBit);
+ temp1[4] = temp0[4] + temp0[5];
+ temp1[5] = -temp0[5] + temp0[4];
+ temp1[6] = -temp0[6] + temp0[7];
+ temp1[7] = temp0[7] + temp0[6];
+ temp1[8] = temp0[8];
+ temp1[9] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[9], cospi[48], temp0[14], cosBit);
+ temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[10], -cospi[16], temp0[13], cosBit);
+ temp1[11] = temp0[11];
+ temp1[12] = temp0[12];
+ temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[13], -cospi[16], temp0[10], cosBit);
+ temp1[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[14], cospi[48], temp0[9], cosBit);
+ temp1[15] = temp0[15];
+
+ // stage 5
+ temp0[0] = temp1[0];
+ temp0[1] = temp1[1];
+ temp0[2] = temp1[2];
+ temp0[3] = temp1[3];
+ temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[4], cospi[8], temp1[7], cosBit);
+ temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[5], cospi[40], temp1[6], cosBit);
+ temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[6], -cospi[40], temp1[5], cosBit);
+ temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[7], -cospi[8], temp1[4], cosBit);
+ temp0[8] = temp1[8] + temp1[9];
+ temp0[9] = -temp1[9] + temp1[8];
+ temp0[10] = -temp1[10] + temp1[11];
+ temp0[11] = temp1[11] + temp1[10];
+ temp0[12] = temp1[12] + temp1[13];
+ temp0[13] = -temp1[13] + temp1[12];
+ temp0[14] = -temp1[14] + temp1[15];
+ temp0[15] = temp1[15] + temp1[14];
+
+ // stage 6
+ temp1[0] = temp0[0];
+ temp1[1] = temp0[1];
+ temp1[2] = temp0[2];
+ temp1[3] = temp0[3];
+ temp1[4] = temp0[4];
+ temp1[5] = temp0[5];
+ temp1[6] = temp0[6];
+ temp1[7] = temp0[7];
+ temp1[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[8], cospi[4], temp0[15], cosBit);
+ temp1[9] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[9], cospi[36], temp0[14], cosBit);
+ temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[10], cospi[20], temp0[13], cosBit);
+ temp1[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[11], cospi[52], temp0[12], cosBit);
+ temp1[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[12], -cospi[52], temp0[11], cosBit);
+ temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[13], -cospi[20], temp0[10], cosBit);
+ temp1[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[14], -cospi[36], temp0[9], cosBit);
+ temp1[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[15], -cospi[4], temp0[8], cosBit);
+
+ // stage 7
+ output = temp1[0];
+ Unsafe.Add(ref output, 1) = temp1[8];
+ Unsafe.Add(ref output, 2) = temp1[4];
+ Unsafe.Add(ref output, 3) = temp1[12];
+ Unsafe.Add(ref output, 4) = temp1[2];
+ Unsafe.Add(ref output, 5) = temp1[10];
+ Unsafe.Add(ref output, 6) = temp1[6];
+ Unsafe.Add(ref output, 7) = temp1[14];
+ Unsafe.Add(ref output, 8) = temp1[1];
+ Unsafe.Add(ref output, 9) = temp1[9];
+ Unsafe.Add(ref output, 10) = temp1[5];
+ Unsafe.Add(ref output, 11) = temp1[13];
+ Unsafe.Add(ref output, 12) = temp1[3];
+ Unsafe.Add(ref output, 13) = temp1[11];
+ Unsafe.Add(ref output, 14) = temp1[7];
+ Unsafe.Add(ref output, 15) = temp1[15];
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs
new file mode 100644
index 0000000000..2b49035f29
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs
@@ -0,0 +1,331 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+
+internal class Av1Dct32Forward1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 32, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 32, nameof(output));
+ TransformScalar(ref input[0], ref output[0], cosBit);
+ }
+
+ private static void TransformScalar(ref int input, ref int output, int cosBit)
+ {
+ Span temp0 = stackalloc int[32];
+ Span temp1 = stackalloc int[32];
+
+ // stage 0;
+
+ // stage 1;
+ temp0[0] = Unsafe.Add(ref input, 0) + Unsafe.Add(ref input, 31);
+ temp0[1] = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 30);
+ temp0[2] = Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 29);
+ temp0[3] = Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 28);
+ temp0[4] = Unsafe.Add(ref input, 4) + Unsafe.Add(ref input, 27);
+ temp0[5] = Unsafe.Add(ref input, 5) + Unsafe.Add(ref input, 26);
+ temp0[6] = Unsafe.Add(ref input, 6) + Unsafe.Add(ref input, 25);
+ temp0[7] = Unsafe.Add(ref input, 7) + Unsafe.Add(ref input, 24);
+ temp0[8] = Unsafe.Add(ref input, 8) + Unsafe.Add(ref input, 23);
+ temp0[9] = Unsafe.Add(ref input, 9) + Unsafe.Add(ref input, 22);
+ temp0[10] = Unsafe.Add(ref input, 10) + Unsafe.Add(ref input, 21);
+ temp0[11] = Unsafe.Add(ref input, 11) + Unsafe.Add(ref input, 20);
+ temp0[12] = Unsafe.Add(ref input, 12) + Unsafe.Add(ref input, 19);
+ temp0[13] = Unsafe.Add(ref input, 13) + Unsafe.Add(ref input, 18);
+ temp0[14] = Unsafe.Add(ref input, 14) + Unsafe.Add(ref input, 17);
+ temp0[15] = Unsafe.Add(ref input, 15) + Unsafe.Add(ref input, 16);
+ temp0[16] = -Unsafe.Add(ref input, 16) + Unsafe.Add(ref input, 15);
+ temp0[17] = -Unsafe.Add(ref input, 17) + Unsafe.Add(ref input, 14);
+ temp0[18] = -Unsafe.Add(ref input, 18) + Unsafe.Add(ref input, 13);
+ temp0[19] = -Unsafe.Add(ref input, 19) + Unsafe.Add(ref input, 12);
+ temp0[20] = -Unsafe.Add(ref input, 20) + Unsafe.Add(ref input, 11);
+ temp0[21] = -Unsafe.Add(ref input, 21) + Unsafe.Add(ref input, 10);
+ temp0[22] = -Unsafe.Add(ref input, 22) + Unsafe.Add(ref input, 9);
+ temp0[23] = -Unsafe.Add(ref input, 23) + Unsafe.Add(ref input, 8);
+ temp0[24] = -Unsafe.Add(ref input, 24) + Unsafe.Add(ref input, 7);
+ temp0[25] = -Unsafe.Add(ref input, 25) + Unsafe.Add(ref input, 6);
+ temp0[26] = -Unsafe.Add(ref input, 26) + Unsafe.Add(ref input, 5);
+ temp0[27] = -Unsafe.Add(ref input, 27) + Unsafe.Add(ref input, 4);
+ temp0[28] = -Unsafe.Add(ref input, 28) + Unsafe.Add(ref input, 3);
+ temp0[29] = -Unsafe.Add(ref input, 29) + Unsafe.Add(ref input, 2);
+ temp0[30] = -Unsafe.Add(ref input, 30) + Unsafe.Add(ref input, 1);
+ temp0[31] = -Unsafe.Add(ref input, 31) + Unsafe.Add(ref input, 0);
+
+ // stage 2
+ Span cospi = Av1SinusConstants.CosinusPi(cosBit);
+ temp1[0] = temp0[0] + temp0[15];
+ temp1[1] = temp0[1] + temp0[14];
+ temp1[2] = temp0[2] + temp0[13];
+ temp1[3] = temp0[3] + temp0[12];
+ temp1[4] = temp0[4] + temp0[11];
+ temp1[5] = temp0[5] + temp0[10];
+ temp1[6] = temp0[6] + temp0[9];
+ temp1[7] = temp0[7] + temp0[8];
+ temp1[8] = -temp0[8] + temp0[7];
+ temp1[9] = -temp0[9] + temp0[6];
+ temp1[10] = -temp0[10] + temp0[5];
+ temp1[11] = -temp0[11] + temp0[4];
+ temp1[12] = -temp0[12] + temp0[3];
+ temp1[13] = -temp0[13] + temp0[2];
+ temp1[14] = -temp0[14] + temp0[1];
+ temp1[15] = -temp0[15] + temp0[0];
+ temp1[16] = temp0[16];
+ temp1[17] = temp0[17];
+ temp1[18] = temp0[18];
+ temp1[19] = temp0[19];
+ temp1[20] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[20], cospi[32], temp0[27], cosBit);
+ temp1[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[21], cospi[32], temp0[26], cosBit);
+ temp1[22] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[22], cospi[32], temp0[25], cosBit);
+ temp1[23] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[23], cospi[32], temp0[24], cosBit);
+ temp1[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[24], cospi[32], temp0[23], cosBit);
+ temp1[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[25], cospi[32], temp0[22], cosBit);
+ temp1[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[26], cospi[32], temp0[21], cosBit);
+ temp1[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[27], cospi[32], temp0[20], cosBit);
+ temp1[28] = temp0[28];
+ temp1[29] = temp0[29];
+ temp1[30] = temp0[30];
+ temp1[31] = temp0[31];
+
+ // stage 3
+ temp0[0] = temp1[0] + temp1[7];
+ temp0[1] = temp1[1] + temp1[6];
+ temp0[2] = temp1[2] + temp1[5];
+ temp0[3] = temp1[3] + temp1[4];
+ temp0[4] = -temp1[4] + temp1[3];
+ temp0[5] = -temp1[5] + temp1[2];
+ temp0[6] = -temp1[6] + temp1[1];
+ temp0[7] = -temp1[7] + temp1[0];
+ temp0[8] = temp1[8];
+ temp0[9] = temp1[9];
+ temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[10], cospi[32], temp1[13], cosBit);
+ temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[11], cospi[32], temp1[12], cosBit);
+ temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[12], cospi[32], temp1[11], cosBit);
+ temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[13], cospi[32], temp1[10], cosBit);
+ temp0[14] = temp1[14];
+ temp0[15] = temp1[15];
+ temp0[16] = temp1[16] + temp1[23];
+ temp0[17] = temp1[17] + temp1[22];
+ temp0[18] = temp1[18] + temp1[21];
+ temp0[19] = temp1[19] + temp1[20];
+ temp0[20] = -temp1[20] + temp1[19];
+ temp0[21] = -temp1[21] + temp1[18];
+ temp0[22] = -temp1[22] + temp1[17];
+ temp0[23] = -temp1[23] + temp1[16];
+ temp0[24] = -temp1[24] + temp1[31];
+ temp0[25] = -temp1[25] + temp1[30];
+ temp0[26] = -temp1[26] + temp1[29];
+ temp0[27] = -temp1[27] + temp1[28];
+ temp0[28] = temp1[28] + temp1[27];
+ temp0[29] = temp1[29] + temp1[26];
+ temp0[30] = temp1[30] + temp1[25];
+ temp0[31] = temp1[31] + temp1[24];
+
+ // stage 4
+ temp1[0] = temp0[0] + temp0[3];
+ temp1[1] = temp0[1] + temp0[2];
+ temp1[2] = -temp0[2] + temp0[1];
+ temp1[3] = -temp0[3] + temp0[0];
+ temp1[4] = temp0[4];
+ temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[5], cospi[32], temp0[6], cosBit);
+ temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[6], cospi[32], temp0[5], cosBit);
+ temp1[7] = temp0[7];
+ temp1[8] = temp0[8] + temp0[11];
+ temp1[9] = temp0[9] + temp0[10];
+ temp1[10] = -temp0[10] + temp0[9];
+ temp1[11] = -temp0[11] + temp0[8];
+ temp1[12] = -temp0[12] + temp0[15];
+ temp1[13] = -temp0[13] + temp0[14];
+ temp1[14] = temp0[14] + temp0[13];
+ temp1[15] = temp0[15] + temp0[12];
+ temp1[16] = temp0[16];
+ temp1[17] = temp0[17];
+ temp1[18] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[18], cospi[48], temp0[29], cosBit);
+ temp1[19] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[19], cospi[48], temp0[28], cosBit);
+ temp1[20] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[20], -cospi[16], temp0[27], cosBit);
+ temp1[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[21], -cospi[16], temp0[26], cosBit);
+ temp1[22] = temp0[22];
+ temp1[23] = temp0[23];
+ temp1[24] = temp0[24];
+ temp1[25] = temp0[25];
+ temp1[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[26], -cospi[16], temp0[21], cosBit);
+ temp1[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[27], -cospi[16], temp0[20], cosBit);
+ temp1[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[28], cospi[48], temp0[19], cosBit);
+ temp1[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[29], cospi[48], temp0[18], cosBit);
+ temp1[30] = temp0[30];
+ temp1[31] = temp0[31];
+
+ // stage 5
+ temp0[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[0], cospi[32], temp1[1], cosBit);
+ temp0[1] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[1], cospi[32], temp1[0], cosBit);
+ temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[2], cospi[16], temp1[3], cosBit);
+ temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[3], -cospi[16], temp1[2], cosBit);
+ temp0[4] = temp1[4] + temp1[5];
+ temp0[5] = -temp1[5] + temp1[4];
+ temp0[6] = -temp1[6] + temp1[7];
+ temp0[7] = temp1[7] + temp1[6];
+ temp0[8] = temp1[8];
+ temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[9], cospi[48], temp1[14], cosBit);
+ temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[10], -cospi[16], temp1[13], cosBit);
+ temp0[11] = temp1[11];
+ temp0[12] = temp1[12];
+ temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[13], -cospi[16], temp1[10], cosBit);
+ temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[14], cospi[48], temp1[9], cosBit);
+ temp0[15] = temp1[15];
+ temp0[16] = temp1[16] + temp1[19];
+ temp0[17] = temp1[17] + temp1[18];
+ temp0[18] = -temp1[18] + temp1[17];
+ temp0[19] = -temp1[19] + temp1[16];
+ temp0[20] = -temp1[20] + temp1[23];
+ temp0[21] = -temp1[21] + temp1[22];
+ temp0[22] = temp1[22] + temp1[21];
+ temp0[23] = temp1[23] + temp1[20];
+ temp0[24] = temp1[24] + temp1[27];
+ temp0[25] = temp1[25] + temp1[26];
+ temp0[26] = -temp1[26] + temp1[25];
+ temp0[27] = -temp1[27] + temp1[24];
+ temp0[28] = -temp1[28] + temp1[31];
+ temp0[29] = -temp1[29] + temp1[30];
+ temp0[30] = temp1[30] + temp1[29];
+ temp0[31] = temp1[31] + temp1[28];
+
+ // stage 6
+ temp1[0] = temp0[0];
+ temp1[1] = temp0[1];
+ temp1[2] = temp0[2];
+ temp1[3] = temp0[3];
+ temp1[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[4], cospi[8], temp0[7], cosBit);
+ temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[5], cospi[40], temp0[6], cosBit);
+ temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[6], -cospi[40], temp0[5], cosBit);
+ temp1[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[7], -cospi[8], temp0[4], cosBit);
+ temp1[8] = temp0[8] + temp0[9];
+ temp1[9] = -temp0[9] + temp0[8];
+ temp1[10] = -temp0[10] + temp0[11];
+ temp1[11] = temp0[11] + temp0[10];
+ temp1[12] = temp0[12] + temp0[13];
+ temp1[13] = -temp0[13] + temp0[12];
+ temp1[14] = -temp0[14] + temp0[15];
+ temp1[15] = temp0[15] + temp0[14];
+ temp1[16] = temp0[16];
+ temp1[17] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp0[17], cospi[56], temp0[30], cosBit);
+ temp1[18] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp0[18], -cospi[8], temp0[29], cosBit);
+ temp1[19] = temp0[19];
+ temp1[20] = temp0[20];
+ temp1[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp0[21], cospi[24], temp0[26], cosBit);
+ temp1[22] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp0[22], -cospi[40], temp0[25], cosBit);
+ temp1[23] = temp0[23];
+ temp1[24] = temp0[24];
+ temp1[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[25], -cospi[40], temp0[22], cosBit);
+ temp1[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp0[26], cospi[24], temp0[21], cosBit);
+ temp1[27] = temp0[27];
+ temp1[28] = temp0[28];
+ temp1[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[29], -cospi[8], temp0[18], cosBit);
+ temp1[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp0[30], cospi[56], temp0[17], cosBit);
+ temp1[31] = temp0[31];
+
+ // stage 7
+ temp0[0] = temp1[0];
+ temp0[1] = temp1[1];
+ temp0[2] = temp1[2];
+ temp0[3] = temp1[3];
+ temp0[4] = temp1[4];
+ temp0[5] = temp1[5];
+ temp0[6] = temp1[6];
+ temp0[7] = temp1[7];
+ temp0[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp1[8], cospi[4], temp1[15], cosBit);
+ temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp1[9], cospi[36], temp1[14], cosBit);
+ temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp1[10], cospi[20], temp1[13], cosBit);
+ temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp1[11], cospi[52], temp1[12], cosBit);
+ temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp1[12], -cospi[52], temp1[11], cosBit);
+ temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp1[13], -cospi[20], temp1[10], cosBit);
+ temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp1[14], -cospi[36], temp1[9], cosBit);
+ temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp1[15], -cospi[4], temp1[8], cosBit);
+ temp0[16] = temp1[16] + temp1[17];
+ temp0[17] = -temp1[17] + temp1[16];
+ temp0[18] = -temp1[18] + temp1[19];
+ temp0[19] = temp1[19] + temp1[18];
+ temp0[20] = temp1[20] + temp1[21];
+ temp0[21] = -temp1[21] + temp1[20];
+ temp0[22] = -temp1[22] + temp1[23];
+ temp0[23] = temp1[23] + temp1[22];
+ temp0[24] = temp1[24] + temp1[25];
+ temp0[25] = -temp1[25] + temp1[24];
+ temp0[26] = -temp1[26] + temp1[27];
+ temp0[27] = temp1[27] + temp1[26];
+ temp0[28] = temp1[28] + temp1[29];
+ temp0[29] = -temp1[29] + temp1[28];
+ temp0[30] = -temp1[30] + temp1[31];
+ temp0[31] = temp1[31] + temp1[30];
+
+ // stage 8
+ temp1[0] = temp0[0];
+ temp1[1] = temp0[1];
+ temp1[2] = temp0[2];
+ temp1[3] = temp0[3];
+ temp1[4] = temp0[4];
+ temp1[5] = temp0[5];
+ temp1[6] = temp0[6];
+ temp1[7] = temp0[7];
+ temp1[8] = temp0[8];
+ temp1[9] = temp0[9];
+ temp1[10] = temp0[10];
+ temp1[11] = temp0[11];
+ temp1[12] = temp0[12];
+ temp1[13] = temp0[13];
+ temp1[14] = temp0[14];
+ temp1[15] = temp0[15];
+ temp1[16] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[62], temp0[16], cospi[2], temp0[31], cosBit);
+ temp1[17] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[30], temp0[17], cospi[34], temp0[30], cosBit);
+ temp1[18] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[46], temp0[18], cospi[18], temp0[29], cosBit);
+ temp1[19] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[14], temp0[19], cospi[50], temp0[28], cosBit);
+ temp1[20] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[54], temp0[20], cospi[10], temp0[27], cosBit);
+ temp1[21] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[22], temp0[21], cospi[42], temp0[26], cosBit);
+ temp1[22] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[38], temp0[22], cospi[26], temp0[25], cosBit);
+ temp1[23] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[6], temp0[23], cospi[58], temp0[24], cosBit);
+ temp1[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[6], temp0[24], -cospi[58], temp0[23], cosBit);
+ temp1[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[38], temp0[25], -cospi[26], temp0[22], cosBit);
+ temp1[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[22], temp0[26], -cospi[42], temp0[21], cosBit);
+ temp1[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[54], temp0[27], -cospi[10], temp0[20], cosBit);
+ temp1[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[14], temp0[28], -cospi[50], temp0[19], cosBit);
+ temp1[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[46], temp0[29], -cospi[18], temp0[18], cosBit);
+ temp1[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[30], temp0[30], -cospi[34], temp0[17], cosBit);
+ temp1[31] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[62], temp0[31], -cospi[2], temp0[16], cosBit);
+
+ // stage 9
+ Unsafe.Add(ref output, 0) = temp1[0];
+ Unsafe.Add(ref output, 1) = temp1[16];
+ Unsafe.Add(ref output, 2) = temp1[8];
+ Unsafe.Add(ref output, 3) = temp1[24];
+ Unsafe.Add(ref output, 4) = temp1[4];
+ Unsafe.Add(ref output, 5) = temp1[20];
+ Unsafe.Add(ref output, 6) = temp1[12];
+ Unsafe.Add(ref output, 7) = temp1[28];
+ Unsafe.Add(ref output, 8) = temp1[2];
+ Unsafe.Add(ref output, 9) = temp1[18];
+ Unsafe.Add(ref output, 10) = temp1[10];
+ Unsafe.Add(ref output, 11) = temp1[26];
+ Unsafe.Add(ref output, 12) = temp1[6];
+ Unsafe.Add(ref output, 13) = temp1[22];
+ Unsafe.Add(ref output, 14) = temp1[14];
+ Unsafe.Add(ref output, 15) = temp1[30];
+ Unsafe.Add(ref output, 16) = temp1[1];
+ Unsafe.Add(ref output, 17) = temp1[17];
+ Unsafe.Add(ref output, 18) = temp1[9];
+ Unsafe.Add(ref output, 19) = temp1[25];
+ Unsafe.Add(ref output, 20) = temp1[5];
+ Unsafe.Add(ref output, 21) = temp1[21];
+ Unsafe.Add(ref output, 22) = temp1[13];
+ Unsafe.Add(ref output, 23) = temp1[29];
+ Unsafe.Add(ref output, 24) = temp1[3];
+ Unsafe.Add(ref output, 25) = temp1[19];
+ Unsafe.Add(ref output, 26) = temp1[11];
+ Unsafe.Add(ref output, 27) = temp1[27];
+ Unsafe.Add(ref output, 28) = temp1[7];
+ Unsafe.Add(ref output, 29) = temp1[23];
+ Unsafe.Add(ref output, 30) = temp1[15];
+ Unsafe.Add(ref output, 31) = temp1[31];
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs
new file mode 100644
index 0000000000..d43e2535c0
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+
+internal class Av1Dct4Forward1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 4, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 4, nameof(output));
+ TransformScalar(ref input[0], ref output[0], cosBit);
+ }
+
+ private static void TransformScalar(ref int input, ref int output, int cosBit)
+ {
+ Span cospi = Av1SinusConstants.CosinusPi(cosBit);
+ ref int bf0 = ref output;
+ ref int bf1 = ref output;
+ Span stepSpan = new int[4];
+ ref int step0 = ref stepSpan[0];
+ ref int step1 = ref Unsafe.Add(ref step0, 1);
+ ref int step2 = ref Unsafe.Add(ref step0, 2);
+ ref int step3 = ref Unsafe.Add(ref step0, 3);
+ ref int output1 = ref Unsafe.Add(ref output, 1);
+ ref int output2 = ref Unsafe.Add(ref output, 2);
+ ref int output3 = ref Unsafe.Add(ref output, 3);
+
+ // stage 0;
+
+ // stage 1;
+ output = input + Unsafe.Add(ref input, 3);
+ output1 = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 2);
+ output2 = -Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 1);
+ output3 = -Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 0);
+
+ // stage 2
+ step0 = HalfButterfly(cospi[32], output, cospi[32], output1, cosBit);
+ step1 = HalfButterfly(-cospi[32], output1, cospi[32], output, cosBit);
+ step2 = HalfButterfly(cospi[48], output2, cospi[16], output3, cosBit);
+ step3 = HalfButterfly(cospi[48], output3, -cospi[16], output2, cosBit);
+
+ // stage 3
+ output = step0;
+ output1 = step2;
+ output2 = step1;
+ output3 = step3;
+ }
+
+ internal static int HalfButterfly(int w0, int in0, int w1, int in1, int bit)
+ {
+ long result64 = (long)(w0 * in0) + (w1 * in1);
+ long intermediate = result64 + (1L << (bit - 1));
+
+ // NOTE(david.barker): The value 'result_64' may not necessarily fit
+ // into 32 bits. However, the result of this function is nominally
+ // ROUND_POWER_OF_TWO_64(result_64, bit)
+ // and that is required to fit into stage_range[stage] many bits
+ // (checked by range_check_buf()).
+ //
+ // Here we've unpacked that rounding operation, and it can be shown
+ // that the value of 'intermediate' here *does* fit into 32 bits
+ // for any conformant bitstream.
+ // The upshot is that, if you do all this calculation using
+ // wrapping 32-bit arithmetic instead of (non-wrapping) 64-bit arithmetic,
+ // then you'll still get the correct result.
+ return (int)(intermediate >> bit);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs
new file mode 100644
index 0000000000..57b59cc488
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs
@@ -0,0 +1,751 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+
+internal class Av1Dct64Forward1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 64, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 64, nameof(output));
+ TransformScalar(ref input[0], ref output[0], cosBit);
+ }
+
+ private static void TransformScalar(ref int input, ref int output, int cosBit)
+ {
+ Span temp0 = stackalloc int[64];
+ Span temp1 = stackalloc int[64];
+
+ // stage 0;
+
+ // stage 1;
+ temp0[0] = Unsafe.Add(ref input, 0) + Unsafe.Add(ref input, 63);
+ temp0[1] = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 62);
+ temp0[2] = Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 61);
+ temp0[3] = Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 60);
+ temp0[4] = Unsafe.Add(ref input, 4) + Unsafe.Add(ref input, 59);
+ temp0[5] = Unsafe.Add(ref input, 5) + Unsafe.Add(ref input, 58);
+ temp0[6] = Unsafe.Add(ref input, 6) + Unsafe.Add(ref input, 57);
+ temp0[7] = Unsafe.Add(ref input, 7) + Unsafe.Add(ref input, 56);
+ temp0[8] = Unsafe.Add(ref input, 8) + Unsafe.Add(ref input, 55);
+ temp0[9] = Unsafe.Add(ref input, 9) + Unsafe.Add(ref input, 54);
+ temp0[10] = Unsafe.Add(ref input, 10) + Unsafe.Add(ref input, 53);
+ temp0[11] = Unsafe.Add(ref input, 11) + Unsafe.Add(ref input, 52);
+ temp0[12] = Unsafe.Add(ref input, 12) + Unsafe.Add(ref input, 51);
+ temp0[13] = Unsafe.Add(ref input, 13) + Unsafe.Add(ref input, 50);
+ temp0[14] = Unsafe.Add(ref input, 14) + Unsafe.Add(ref input, 49);
+ temp0[15] = Unsafe.Add(ref input, 15) + Unsafe.Add(ref input, 48);
+ temp0[16] = Unsafe.Add(ref input, 16) + Unsafe.Add(ref input, 47);
+ temp0[17] = Unsafe.Add(ref input, 17) + Unsafe.Add(ref input, 46);
+ temp0[18] = Unsafe.Add(ref input, 18) + Unsafe.Add(ref input, 45);
+ temp0[19] = Unsafe.Add(ref input, 19) + Unsafe.Add(ref input, 44);
+ temp0[20] = Unsafe.Add(ref input, 20) + Unsafe.Add(ref input, 43);
+ temp0[21] = Unsafe.Add(ref input, 21) + Unsafe.Add(ref input, 42);
+ temp0[22] = Unsafe.Add(ref input, 22) + Unsafe.Add(ref input, 41);
+ temp0[23] = Unsafe.Add(ref input, 23) + Unsafe.Add(ref input, 40);
+ temp0[24] = Unsafe.Add(ref input, 24) + Unsafe.Add(ref input, 39);
+ temp0[25] = Unsafe.Add(ref input, 25) + Unsafe.Add(ref input, 38);
+ temp0[26] = Unsafe.Add(ref input, 26) + Unsafe.Add(ref input, 37);
+ temp0[27] = Unsafe.Add(ref input, 27) + Unsafe.Add(ref input, 36);
+ temp0[28] = Unsafe.Add(ref input, 28) + Unsafe.Add(ref input, 35);
+ temp0[29] = Unsafe.Add(ref input, 29) + Unsafe.Add(ref input, 34);
+ temp0[30] = Unsafe.Add(ref input, 30) + Unsafe.Add(ref input, 33);
+ temp0[31] = Unsafe.Add(ref input, 31) + Unsafe.Add(ref input, 32);
+ temp0[32] = -Unsafe.Add(ref input, 32) + Unsafe.Add(ref input, 31);
+ temp0[33] = -Unsafe.Add(ref input, 33) + Unsafe.Add(ref input, 30);
+ temp0[34] = -Unsafe.Add(ref input, 34) + Unsafe.Add(ref input, 29);
+ temp0[35] = -Unsafe.Add(ref input, 35) + Unsafe.Add(ref input, 28);
+ temp0[36] = -Unsafe.Add(ref input, 36) + Unsafe.Add(ref input, 27);
+ temp0[37] = -Unsafe.Add(ref input, 37) + Unsafe.Add(ref input, 26);
+ temp0[38] = -Unsafe.Add(ref input, 38) + Unsafe.Add(ref input, 25);
+ temp0[39] = -Unsafe.Add(ref input, 39) + Unsafe.Add(ref input, 24);
+ temp0[40] = -Unsafe.Add(ref input, 40) + Unsafe.Add(ref input, 23);
+ temp0[41] = -Unsafe.Add(ref input, 41) + Unsafe.Add(ref input, 22);
+ temp0[42] = -Unsafe.Add(ref input, 42) + Unsafe.Add(ref input, 21);
+ temp0[43] = -Unsafe.Add(ref input, 43) + Unsafe.Add(ref input, 20);
+ temp0[44] = -Unsafe.Add(ref input, 44) + Unsafe.Add(ref input, 19);
+ temp0[45] = -Unsafe.Add(ref input, 45) + Unsafe.Add(ref input, 18);
+ temp0[46] = -Unsafe.Add(ref input, 46) + Unsafe.Add(ref input, 17);
+ temp0[47] = -Unsafe.Add(ref input, 47) + Unsafe.Add(ref input, 16);
+ temp0[48] = -Unsafe.Add(ref input, 48) + Unsafe.Add(ref input, 15);
+ temp0[49] = -Unsafe.Add(ref input, 49) + Unsafe.Add(ref input, 14);
+ temp0[50] = -Unsafe.Add(ref input, 50) + Unsafe.Add(ref input, 13);
+ temp0[51] = -Unsafe.Add(ref input, 51) + Unsafe.Add(ref input, 12);
+ temp0[52] = -Unsafe.Add(ref input, 52) + Unsafe.Add(ref input, 11);
+ temp0[53] = -Unsafe.Add(ref input, 53) + Unsafe.Add(ref input, 10);
+ temp0[54] = -Unsafe.Add(ref input, 54) + Unsafe.Add(ref input, 9);
+ temp0[55] = -Unsafe.Add(ref input, 55) + Unsafe.Add(ref input, 8);
+ temp0[56] = -Unsafe.Add(ref input, 56) + Unsafe.Add(ref input, 7);
+ temp0[57] = -Unsafe.Add(ref input, 57) + Unsafe.Add(ref input, 6);
+ temp0[58] = -Unsafe.Add(ref input, 58) + Unsafe.Add(ref input, 5);
+ temp0[59] = -Unsafe.Add(ref input, 59) + Unsafe.Add(ref input, 4);
+ temp0[60] = -Unsafe.Add(ref input, 60) + Unsafe.Add(ref input, 3);
+ temp0[61] = -Unsafe.Add(ref input, 61) + Unsafe.Add(ref input, 2);
+ temp0[62] = -Unsafe.Add(ref input, 62) + Unsafe.Add(ref input, 1);
+ temp0[63] = -Unsafe.Add(ref input, 63) + Unsafe.Add(ref input, 0);
+
+ // stage 2
+ Span cospi = Av1SinusConstants.CosinusPi(cosBit);
+ temp1[0] = temp0[0] + temp0[31];
+ temp1[1] = temp0[1] + temp0[30];
+ temp1[2] = temp0[2] + temp0[29];
+ temp1[3] = temp0[3] + temp0[28];
+ temp1[4] = temp0[4] + temp0[27];
+ temp1[5] = temp0[5] + temp0[26];
+ temp1[6] = temp0[6] + temp0[25];
+ temp1[7] = temp0[7] + temp0[24];
+ temp1[8] = temp0[8] + temp0[23];
+ temp1[9] = temp0[9] + temp0[22];
+ temp1[10] = temp0[10] + temp0[21];
+ temp1[11] = temp0[11] + temp0[20];
+ temp1[12] = temp0[12] + temp0[19];
+ temp1[13] = temp0[13] + temp0[18];
+ temp1[14] = temp0[14] + temp0[17];
+ temp1[15] = temp0[15] + temp0[16];
+ temp1[16] = -temp0[16] + temp0[15];
+ temp1[17] = -temp0[17] + temp0[14];
+ temp1[18] = -temp0[18] + temp0[13];
+ temp1[19] = -temp0[19] + temp0[12];
+ temp1[20] = -temp0[20] + temp0[11];
+ temp1[21] = -temp0[21] + temp0[10];
+ temp1[22] = -temp0[22] + temp0[9];
+ temp1[23] = -temp0[23] + temp0[8];
+ temp1[24] = -temp0[24] + temp0[7];
+ temp1[25] = -temp0[25] + temp0[6];
+ temp1[26] = -temp0[26] + temp0[5];
+ temp1[27] = -temp0[27] + temp0[4];
+ temp1[28] = -temp0[28] + temp0[3];
+ temp1[29] = -temp0[29] + temp0[2];
+ temp1[30] = -temp0[30] + temp0[1];
+ temp1[31] = -temp0[31] + temp0[0];
+ temp1[32] = temp0[32];
+ temp1[33] = temp0[33];
+ temp1[34] = temp0[34];
+ temp1[35] = temp0[35];
+ temp1[36] = temp0[36];
+ temp1[37] = temp0[37];
+ temp1[38] = temp0[38];
+ temp1[39] = temp0[39];
+ temp1[40] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[40], cospi[32], temp0[55], cosBit);
+ temp1[41] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[41], cospi[32], temp0[54], cosBit);
+ temp1[42] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[42], cospi[32], temp0[53], cosBit);
+ temp1[43] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[43], cospi[32], temp0[52], cosBit);
+ temp1[44] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[44], cospi[32], temp0[51], cosBit);
+ temp1[45] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[45], cospi[32], temp0[50], cosBit);
+ temp1[46] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[46], cospi[32], temp0[49], cosBit);
+ temp1[47] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[47], cospi[32], temp0[48], cosBit);
+ temp1[48] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[48], cospi[32], temp0[47], cosBit);
+ temp1[49] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[49], cospi[32], temp0[46], cosBit);
+ temp1[50] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[50], cospi[32], temp0[45], cosBit);
+ temp1[51] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[51], cospi[32], temp0[44], cosBit);
+ temp1[52] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[52], cospi[32], temp0[43], cosBit);
+ temp1[53] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[53], cospi[32], temp0[42], cosBit);
+ temp1[54] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[54], cospi[32], temp0[41], cosBit);
+ temp1[55] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[55], cospi[32], temp0[40], cosBit);
+ temp1[56] = temp0[56];
+ temp1[57] = temp0[57];
+ temp1[58] = temp0[58];
+ temp1[59] = temp0[59];
+ temp1[60] = temp0[60];
+ temp1[61] = temp0[61];
+ temp1[62] = temp0[62];
+ temp1[63] = temp0[63];
+
+ // stage 3
+ temp0[0] = temp1[0] + temp1[15];
+ temp0[1] = temp1[1] + temp1[14];
+ temp0[2] = temp1[2] + temp1[13];
+ temp0[3] = temp1[3] + temp1[12];
+ temp0[4] = temp1[4] + temp1[11];
+ temp0[5] = temp1[5] + temp1[10];
+ temp0[6] = temp1[6] + temp1[9];
+ temp0[7] = temp1[7] + temp1[8];
+ temp0[8] = -temp1[8] + temp1[7];
+ temp0[9] = -temp1[9] + temp1[6];
+ temp0[10] = -temp1[10] + temp1[5];
+ temp0[11] = -temp1[11] + temp1[4];
+ temp0[12] = -temp1[12] + temp1[3];
+ temp0[13] = -temp1[13] + temp1[2];
+ temp0[14] = -temp1[14] + temp1[1];
+ temp0[15] = -temp1[15] + temp1[0];
+ temp0[16] = temp1[16];
+ temp0[17] = temp1[17];
+ temp0[18] = temp1[18];
+ temp0[19] = temp1[19];
+ temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[20], cospi[32], temp1[27], cosBit);
+ temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[21], cospi[32], temp1[26], cosBit);
+ temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[22], cospi[32], temp1[25], cosBit);
+ temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[23], cospi[32], temp1[24], cosBit);
+ temp0[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[24], cospi[32], temp1[23], cosBit);
+ temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[25], cospi[32], temp1[22], cosBit);
+ temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[26], cospi[32], temp1[21], cosBit);
+ temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[27], cospi[32], temp1[20], cosBit);
+ temp0[28] = temp1[28];
+ temp0[29] = temp1[29];
+ temp0[30] = temp1[30];
+ temp0[31] = temp1[31];
+ temp0[32] = temp1[32] + temp1[47];
+ temp0[33] = temp1[33] + temp1[46];
+ temp0[34] = temp1[34] + temp1[45];
+ temp0[35] = temp1[35] + temp1[44];
+ temp0[36] = temp1[36] + temp1[43];
+ temp0[37] = temp1[37] + temp1[42];
+ temp0[38] = temp1[38] + temp1[41];
+ temp0[39] = temp1[39] + temp1[40];
+ temp0[40] = -temp1[40] + temp1[39];
+ temp0[41] = -temp1[41] + temp1[38];
+ temp0[42] = -temp1[42] + temp1[37];
+ temp0[43] = -temp1[43] + temp1[36];
+ temp0[44] = -temp1[44] + temp1[35];
+ temp0[45] = -temp1[45] + temp1[34];
+ temp0[46] = -temp1[46] + temp1[33];
+ temp0[47] = -temp1[47] + temp1[32];
+ temp0[48] = -temp1[48] + temp1[63];
+ temp0[49] = -temp1[49] + temp1[62];
+ temp0[50] = -temp1[50] + temp1[61];
+ temp0[51] = -temp1[51] + temp1[60];
+ temp0[52] = -temp1[52] + temp1[59];
+ temp0[53] = -temp1[53] + temp1[58];
+ temp0[54] = -temp1[54] + temp1[57];
+ temp0[55] = -temp1[55] + temp1[56];
+ temp0[56] = temp1[56] + temp1[55];
+ temp0[57] = temp1[57] + temp1[54];
+ temp0[58] = temp1[58] + temp1[53];
+ temp0[59] = temp1[59] + temp1[52];
+ temp0[60] = temp1[60] + temp1[51];
+ temp0[61] = temp1[61] + temp1[50];
+ temp0[62] = temp1[62] + temp1[49];
+ temp0[63] = temp1[63] + temp1[48];
+
+ // stage 4
+ temp1[0] = temp0[0] + temp0[7];
+ temp1[1] = temp0[1] + temp0[6];
+ temp1[2] = temp0[2] + temp0[5];
+ temp1[3] = temp0[3] + temp0[4];
+ temp1[4] = -temp0[4] + temp0[3];
+ temp1[5] = -temp0[5] + temp0[2];
+ temp1[6] = -temp0[6] + temp0[1];
+ temp1[7] = -temp0[7] + temp0[0];
+ temp1[8] = temp0[8];
+ temp1[9] = temp0[9];
+ temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[10], cospi[32], temp0[13], cosBit);
+ temp1[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[11], cospi[32], temp0[12], cosBit);
+ temp1[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[12], cospi[32], temp0[11], cosBit);
+ temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[13], cospi[32], temp0[10], cosBit);
+ temp1[14] = temp0[14];
+ temp1[15] = temp0[15];
+ temp1[16] = temp0[16] + temp0[23];
+ temp1[17] = temp0[17] + temp0[22];
+ temp1[18] = temp0[18] + temp0[21];
+ temp1[19] = temp0[19] + temp0[20];
+ temp1[20] = -temp0[20] + temp0[19];
+ temp1[21] = -temp0[21] + temp0[18];
+ temp1[22] = -temp0[22] + temp0[17];
+ temp1[23] = -temp0[23] + temp0[16];
+ temp1[24] = -temp0[24] + temp0[31];
+ temp1[25] = -temp0[25] + temp0[30];
+ temp1[26] = -temp0[26] + temp0[29];
+ temp1[27] = -temp0[27] + temp0[28];
+ temp1[28] = temp0[28] + temp0[27];
+ temp1[29] = temp0[29] + temp0[26];
+ temp1[30] = temp0[30] + temp0[25];
+ temp1[31] = temp0[31] + temp0[24];
+ temp1[32] = temp0[32];
+ temp1[33] = temp0[33];
+ temp1[34] = temp0[34];
+ temp1[35] = temp0[35];
+ temp1[36] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[36], cospi[48], temp0[59], cosBit);
+ temp1[37] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[37], cospi[48], temp0[58], cosBit);
+ temp1[38] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[38], cospi[48], temp0[57], cosBit);
+ temp1[39] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[39], cospi[48], temp0[56], cosBit);
+ temp1[40] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[40], -cospi[16], temp0[55], cosBit);
+ temp1[41] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[41], -cospi[16], temp0[54], cosBit);
+ temp1[42] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[42], -cospi[16], temp0[53], cosBit);
+ temp1[43] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[43], -cospi[16], temp0[52], cosBit);
+ temp1[44] = temp0[44];
+ temp1[45] = temp0[45];
+ temp1[46] = temp0[46];
+ temp1[47] = temp0[47];
+ temp1[48] = temp0[48];
+ temp1[49] = temp0[49];
+ temp1[50] = temp0[50];
+ temp1[51] = temp0[51];
+ temp1[52] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[52], -cospi[16], temp0[43], cosBit);
+ temp1[53] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[53], -cospi[16], temp0[42], cosBit);
+ temp1[54] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[54], -cospi[16], temp0[41], cosBit);
+ temp1[55] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[55], -cospi[16], temp0[40], cosBit);
+ temp1[56] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[56], cospi[48], temp0[39], cosBit);
+ temp1[57] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[57], cospi[48], temp0[38], cosBit);
+ temp1[58] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[58], cospi[48], temp0[37], cosBit);
+ temp1[59] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[59], cospi[48], temp0[36], cosBit);
+ temp1[60] = temp0[60];
+ temp1[61] = temp0[61];
+ temp1[62] = temp0[62];
+ temp1[63] = temp0[63];
+
+ // stage 5
+ temp0[0] = temp1[0] + temp1[3];
+ temp0[1] = temp1[1] + temp1[2];
+ temp0[2] = -temp1[2] + temp1[1];
+ temp0[3] = -temp1[3] + temp1[0];
+ temp0[4] = temp1[4];
+ temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[5], cospi[32], temp1[6], cosBit);
+ temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[6], cospi[32], temp1[5], cosBit);
+ temp0[7] = temp1[7];
+ temp0[8] = temp1[8] + temp1[11];
+ temp0[9] = temp1[9] + temp1[10];
+ temp0[10] = -temp1[10] + temp1[9];
+ temp0[11] = -temp1[11] + temp1[8];
+ temp0[12] = -temp1[12] + temp1[15];
+ temp0[13] = -temp1[13] + temp1[14];
+ temp0[14] = temp1[14] + temp1[13];
+ temp0[15] = temp1[15] + temp1[12];
+ temp0[16] = temp1[16];
+ temp0[17] = temp1[17];
+ temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[18], cospi[48], temp1[29], cosBit);
+ temp0[19] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[19], cospi[48], temp1[28], cosBit);
+ temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[20], -cospi[16], temp1[27], cosBit);
+ temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[21], -cospi[16], temp1[26], cosBit);
+ temp0[22] = temp1[22];
+ temp0[23] = temp1[23];
+ temp0[24] = temp1[24];
+ temp0[25] = temp1[25];
+ temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[26], -cospi[16], temp1[21], cosBit);
+ temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[27], -cospi[16], temp1[20], cosBit);
+ temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[28], cospi[48], temp1[19], cosBit);
+ temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[29], cospi[48], temp1[18], cosBit);
+ temp0[30] = temp1[30];
+ temp0[31] = temp1[31];
+ temp0[32] = temp1[32] + temp1[39];
+ temp0[33] = temp1[33] + temp1[38];
+ temp0[34] = temp1[34] + temp1[37];
+ temp0[35] = temp1[35] + temp1[36];
+ temp0[36] = -temp1[36] + temp1[35];
+ temp0[37] = -temp1[37] + temp1[34];
+ temp0[38] = -temp1[38] + temp1[33];
+ temp0[39] = -temp1[39] + temp1[32];
+ temp0[40] = -temp1[40] + temp1[47];
+ temp0[41] = -temp1[41] + temp1[46];
+ temp0[42] = -temp1[42] + temp1[45];
+ temp0[43] = -temp1[43] + temp1[44];
+ temp0[44] = temp1[44] + temp1[43];
+ temp0[45] = temp1[45] + temp1[42];
+ temp0[46] = temp1[46] + temp1[41];
+ temp0[47] = temp1[47] + temp1[40];
+ temp0[48] = temp1[48] + temp1[55];
+ temp0[49] = temp1[49] + temp1[54];
+ temp0[50] = temp1[50] + temp1[53];
+ temp0[51] = temp1[51] + temp1[52];
+ temp0[52] = -temp1[52] + temp1[51];
+ temp0[53] = -temp1[53] + temp1[50];
+ temp0[54] = -temp1[54] + temp1[49];
+ temp0[55] = -temp1[55] + temp1[48];
+ temp0[56] = -temp1[56] + temp1[63];
+ temp0[57] = -temp1[57] + temp1[62];
+ temp0[58] = -temp1[58] + temp1[61];
+ temp0[59] = -temp1[59] + temp1[60];
+ temp0[60] = temp1[60] + temp1[59];
+ temp0[61] = temp1[61] + temp1[58];
+ temp0[62] = temp1[62] + temp1[57];
+ temp0[63] = temp1[63] + temp1[56];
+
+ // stage 6
+ temp1[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[0], cospi[32], temp0[1], cosBit);
+ temp1[1] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[1], cospi[32], temp0[0], cosBit);
+ temp1[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[2], cospi[16], temp0[3], cosBit);
+ temp1[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[3], -cospi[16], temp0[2], cosBit);
+ temp1[4] = temp0[4] + temp0[5];
+ temp1[5] = -temp0[5] + temp0[4];
+ temp1[6] = -temp0[6] + temp0[7];
+ temp1[7] = temp0[7] + temp0[6];
+ temp1[8] = temp0[8];
+ temp1[9] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[9], cospi[48], temp0[14], cosBit);
+ temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[10], -cospi[16], temp0[13], cosBit);
+ temp1[11] = temp0[11];
+ temp1[12] = temp0[12];
+ temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[13], -cospi[16], temp0[10], cosBit);
+ temp1[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[14], cospi[48], temp0[9], cosBit);
+ temp1[15] = temp0[15];
+ temp1[16] = temp0[16] + temp0[19];
+ temp1[17] = temp0[17] + temp0[18];
+ temp1[18] = -temp0[18] + temp0[17];
+ temp1[19] = -temp0[19] + temp0[16];
+ temp1[20] = -temp0[20] + temp0[23];
+ temp1[21] = -temp0[21] + temp0[22];
+ temp1[22] = temp0[22] + temp0[21];
+ temp1[23] = temp0[23] + temp0[20];
+ temp1[24] = temp0[24] + temp0[27];
+ temp1[25] = temp0[25] + temp0[26];
+ temp1[26] = -temp0[26] + temp0[25];
+ temp1[27] = -temp0[27] + temp0[24];
+ temp1[28] = -temp0[28] + temp0[31];
+ temp1[29] = -temp0[29] + temp0[30];
+ temp1[30] = temp0[30] + temp0[29];
+ temp1[31] = temp0[31] + temp0[28];
+ temp1[32] = temp0[32];
+ temp1[33] = temp0[33];
+ temp1[34] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp0[34], cospi[56], temp0[61], cosBit);
+ temp1[35] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp0[35], cospi[56], temp0[60], cosBit);
+ temp1[36] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp0[36], -cospi[8], temp0[59], cosBit);
+ temp1[37] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp0[37], -cospi[8], temp0[58], cosBit);
+ temp1[38] = temp0[38];
+ temp1[39] = temp0[39];
+ temp1[40] = temp0[40];
+ temp1[41] = temp0[41];
+ temp1[42] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp0[42], cospi[24], temp0[53], cosBit);
+ temp1[43] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp0[43], cospi[24], temp0[52], cosBit);
+ temp1[44] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp0[44], -cospi[40], temp0[51], cosBit);
+ temp1[45] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp0[45], -cospi[40], temp0[50], cosBit);
+ temp1[46] = temp0[46];
+ temp1[47] = temp0[47];
+ temp1[48] = temp0[48];
+ temp1[49] = temp0[49];
+ temp1[50] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[50], -cospi[40], temp0[45], cosBit);
+ temp1[51] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[51], -cospi[40], temp0[44], cosBit);
+ temp1[52] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp0[52], cospi[24], temp0[43], cosBit);
+ temp1[53] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp0[53], cospi[24], temp0[42], cosBit);
+ temp1[54] = temp0[54];
+ temp1[55] = temp0[55];
+ temp1[56] = temp0[56];
+ temp1[57] = temp0[57];
+ temp1[58] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[58], -cospi[8], temp0[37], cosBit);
+ temp1[59] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[59], -cospi[8], temp0[36], cosBit);
+ temp1[60] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp0[60], cospi[56], temp0[35], cosBit);
+ temp1[61] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp0[61], cospi[56], temp0[34], cosBit);
+ temp1[62] = temp0[62];
+ temp1[63] = temp0[63];
+
+ // stage 7
+ temp0[0] = temp1[0];
+ temp0[1] = temp1[1];
+ temp0[2] = temp1[2];
+ temp0[3] = temp1[3];
+ temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[4], cospi[8], temp1[7], cosBit);
+ temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[5], cospi[40], temp1[6], cosBit);
+ temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[6], -cospi[40], temp1[5], cosBit);
+ temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[7], -cospi[8], temp1[4], cosBit);
+ temp0[8] = temp1[8] + temp1[9];
+ temp0[9] = -temp1[9] + temp1[8];
+ temp0[10] = -temp1[10] + temp1[11];
+ temp0[11] = temp1[11] + temp1[10];
+ temp0[12] = temp1[12] + temp1[13];
+ temp0[13] = -temp1[13] + temp1[12];
+ temp0[14] = -temp1[14] + temp1[15];
+ temp0[15] = temp1[15] + temp1[14];
+ temp0[16] = temp1[16];
+ temp0[17] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp1[17], cospi[56], temp1[30], cosBit);
+ temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp1[18], -cospi[8], temp1[29], cosBit);
+ temp0[19] = temp1[19];
+ temp0[20] = temp1[20];
+ temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp1[21], cospi[24], temp1[26], cosBit);
+ temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp1[22], -cospi[40], temp1[25], cosBit);
+ temp0[23] = temp1[23];
+ temp0[24] = temp1[24];
+ temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[25], -cospi[40], temp1[22], cosBit);
+ temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp1[26], cospi[24], temp1[21], cosBit);
+ temp0[27] = temp1[27];
+ temp0[28] = temp1[28];
+ temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[29], -cospi[8], temp1[18], cosBit);
+ temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp1[30], cospi[56], temp1[17], cosBit);
+ temp0[31] = temp1[31];
+ temp0[32] = temp1[32] + temp1[35];
+ temp0[33] = temp1[33] + temp1[34];
+ temp0[34] = -temp1[34] + temp1[33];
+ temp0[35] = -temp1[35] + temp1[32];
+ temp0[36] = -temp1[36] + temp1[39];
+ temp0[37] = -temp1[37] + temp1[38];
+ temp0[38] = temp1[38] + temp1[37];
+ temp0[39] = temp1[39] + temp1[36];
+ temp0[40] = temp1[40] + temp1[43];
+ temp0[41] = temp1[41] + temp1[42];
+ temp0[42] = -temp1[42] + temp1[41];
+ temp0[43] = -temp1[43] + temp1[40];
+ temp0[44] = -temp1[44] + temp1[47];
+ temp0[45] = -temp1[45] + temp1[46];
+ temp0[46] = temp1[46] + temp1[45];
+ temp0[47] = temp1[47] + temp1[44];
+ temp0[48] = temp1[48] + temp1[51];
+ temp0[49] = temp1[49] + temp1[50];
+ temp0[50] = -temp1[50] + temp1[49];
+ temp0[51] = -temp1[51] + temp1[48];
+ temp0[52] = -temp1[52] + temp1[55];
+ temp0[53] = -temp1[53] + temp1[54];
+ temp0[54] = temp1[54] + temp1[53];
+ temp0[55] = temp1[55] + temp1[52];
+ temp0[56] = temp1[56] + temp1[59];
+ temp0[57] = temp1[57] + temp1[58];
+ temp0[58] = -temp1[58] + temp1[57];
+ temp0[59] = -temp1[59] + temp1[56];
+ temp0[60] = -temp1[60] + temp1[63];
+ temp0[61] = -temp1[61] + temp1[62];
+ temp0[62] = temp1[62] + temp1[61];
+ temp0[63] = temp1[63] + temp1[60];
+
+ // stage 8
+ temp1[0] = temp0[0];
+ temp1[1] = temp0[1];
+ temp1[2] = temp0[2];
+ temp1[3] = temp0[3];
+ temp1[4] = temp0[4];
+ temp1[5] = temp0[5];
+ temp1[6] = temp0[6];
+ temp1[7] = temp0[7];
+ temp1[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[8], cospi[4], temp0[15], cosBit);
+ temp1[9] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[9], cospi[36], temp0[14], cosBit);
+ temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[10], cospi[20], temp0[13], cosBit);
+ temp1[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[11], cospi[52], temp0[12], cosBit);
+ temp1[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[12], -cospi[52], temp0[11], cosBit);
+ temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[13], -cospi[20], temp0[10], cosBit);
+ temp1[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[14], -cospi[36], temp0[9], cosBit);
+ temp1[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[15], -cospi[4], temp0[8], cosBit);
+ temp1[16] = temp0[16] + temp0[17];
+ temp1[17] = -temp0[17] + temp0[16];
+ temp1[18] = -temp0[18] + temp0[19];
+ temp1[19] = temp0[19] + temp0[18];
+ temp1[20] = temp0[20] + temp0[21];
+ temp1[21] = -temp0[21] + temp0[20];
+ temp1[22] = -temp0[22] + temp0[23];
+ temp1[23] = temp0[23] + temp0[22];
+ temp1[24] = temp0[24] + temp0[25];
+ temp1[25] = -temp0[25] + temp0[24];
+ temp1[26] = -temp0[26] + temp0[27];
+ temp1[27] = temp0[27] + temp0[26];
+ temp1[28] = temp0[28] + temp0[29];
+ temp1[29] = -temp0[29] + temp0[28];
+ temp1[30] = -temp0[30] + temp0[31];
+ temp1[31] = temp0[31] + temp0[30];
+ temp1[32] = temp0[32];
+ temp1[33] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[4], temp0[33], cospi[60], temp0[62], cosBit);
+ temp1[34] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[60], temp0[34], -cospi[4], temp0[61], cosBit);
+ temp1[35] = temp0[35];
+ temp1[36] = temp0[36];
+ temp1[37] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[36], temp0[37], cospi[28], temp0[58], cosBit);
+ temp1[38] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[28], temp0[38], -cospi[36], temp0[57], cosBit);
+ temp1[39] = temp0[39];
+ temp1[40] = temp0[40];
+ temp1[41] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[20], temp0[41], cospi[44], temp0[54], cosBit);
+ temp1[42] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[44], temp0[42], -cospi[20], temp0[53], cosBit);
+ temp1[43] = temp0[43];
+ temp1[44] = temp0[44];
+ temp1[45] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[52], temp0[45], cospi[12], temp0[50], cosBit);
+ temp1[46] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[12], temp0[46], -cospi[52], temp0[49], cosBit);
+ temp1[47] = temp0[47];
+ temp1[48] = temp0[48];
+ temp1[49] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[49], -cospi[52], temp0[46], cosBit);
+ temp1[50] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[52], temp0[50], cospi[12], temp0[45], cosBit);
+ temp1[51] = temp0[51];
+ temp1[52] = temp0[52];
+ temp1[53] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[53], -cospi[20], temp0[42], cosBit);
+ temp1[54] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[20], temp0[54], cospi[44], temp0[41], cosBit);
+ temp1[55] = temp0[55];
+ temp1[56] = temp0[56];
+ temp1[57] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[57], -cospi[36], temp0[38], cosBit);
+ temp1[58] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[36], temp0[58], cospi[28], temp0[37], cosBit);
+ temp1[59] = temp0[59];
+ temp1[60] = temp0[60];
+ temp1[61] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[61], -cospi[4], temp0[34], cosBit);
+ temp1[62] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[4], temp0[62], cospi[60], temp0[33], cosBit);
+ temp1[63] = temp0[63];
+
+ // stage 9
+ temp0[0] = temp1[0];
+ temp0[1] = temp1[1];
+ temp0[2] = temp1[2];
+ temp0[3] = temp1[3];
+ temp0[4] = temp1[4];
+ temp0[5] = temp1[5];
+ temp0[6] = temp1[6];
+ temp0[7] = temp1[7];
+ temp0[8] = temp1[8];
+ temp0[9] = temp1[9];
+ temp0[10] = temp1[10];
+ temp0[11] = temp1[11];
+ temp0[12] = temp1[12];
+ temp0[13] = temp1[13];
+ temp0[14] = temp1[14];
+ temp0[15] = temp1[15];
+ temp0[16] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[62], temp1[16], cospi[2], temp1[31], cosBit);
+ temp0[17] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[30], temp1[17], cospi[34], temp1[30], cosBit);
+ temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[46], temp1[18], cospi[18], temp1[29], cosBit);
+ temp0[19] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[14], temp1[19], cospi[50], temp1[28], cosBit);
+ temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[54], temp1[20], cospi[10], temp1[27], cosBit);
+ temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[22], temp1[21], cospi[42], temp1[26], cosBit);
+ temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[38], temp1[22], cospi[26], temp1[25], cosBit);
+ temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[6], temp1[23], cospi[58], temp1[24], cosBit);
+ temp0[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[6], temp1[24], -cospi[58], temp1[23], cosBit);
+ temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[38], temp1[25], -cospi[26], temp1[22], cosBit);
+ temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[22], temp1[26], -cospi[42], temp1[21], cosBit);
+ temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[54], temp1[27], -cospi[10], temp1[20], cosBit);
+ temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[14], temp1[28], -cospi[50], temp1[19], cosBit);
+ temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[46], temp1[29], -cospi[18], temp1[18], cosBit);
+ temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[30], temp1[30], -cospi[34], temp1[17], cosBit);
+ temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[62], temp1[31], -cospi[2], temp1[16], cosBit);
+ temp0[32] = temp1[32] + temp1[33];
+ temp0[33] = -temp1[33] + temp1[32];
+ temp0[34] = -temp1[34] + temp1[35];
+ temp0[35] = temp1[35] + temp1[34];
+ temp0[36] = temp1[36] + temp1[37];
+ temp0[37] = -temp1[37] + temp1[36];
+ temp0[38] = -temp1[38] + temp1[39];
+ temp0[39] = temp1[39] + temp1[38];
+ temp0[40] = temp1[40] + temp1[41];
+ temp0[41] = -temp1[41] + temp1[40];
+ temp0[42] = -temp1[42] + temp1[43];
+ temp0[43] = temp1[43] + temp1[42];
+ temp0[44] = temp1[44] + temp1[45];
+ temp0[45] = -temp1[45] + temp1[44];
+ temp0[46] = -temp1[46] + temp1[47];
+ temp0[47] = temp1[47] + temp1[46];
+ temp0[48] = temp1[48] + temp1[49];
+ temp0[49] = -temp1[49] + temp1[48];
+ temp0[50] = -temp1[50] + temp1[51];
+ temp0[51] = temp1[51] + temp1[50];
+ temp0[52] = temp1[52] + temp1[53];
+ temp0[53] = -temp1[53] + temp1[52];
+ temp0[54] = -temp1[54] + temp1[55];
+ temp0[55] = temp1[55] + temp1[54];
+ temp0[56] = temp1[56] + temp1[57];
+ temp0[57] = -temp1[57] + temp1[56];
+ temp0[58] = -temp1[58] + temp1[59];
+ temp0[59] = temp1[59] + temp1[58];
+ temp0[60] = temp1[60] + temp1[61];
+ temp0[61] = -temp1[61] + temp1[60];
+ temp0[62] = -temp1[62] + temp1[63];
+ temp0[63] = temp1[63] + temp1[62];
+
+ // stage 10
+ temp1[0] = temp0[0];
+ temp1[1] = temp0[1];
+ temp1[2] = temp0[2];
+ temp1[3] = temp0[3];
+ temp1[4] = temp0[4];
+ temp1[5] = temp0[5];
+ temp1[6] = temp0[6];
+ temp1[7] = temp0[7];
+ temp1[8] = temp0[8];
+ temp1[9] = temp0[9];
+ temp1[10] = temp0[10];
+ temp1[11] = temp0[11];
+ temp1[12] = temp0[12];
+ temp1[13] = temp0[13];
+ temp1[14] = temp0[14];
+ temp1[15] = temp0[15];
+ temp1[16] = temp0[16];
+ temp1[17] = temp0[17];
+ temp1[18] = temp0[18];
+ temp1[19] = temp0[19];
+ temp1[20] = temp0[20];
+ temp1[21] = temp0[21];
+ temp1[22] = temp0[22];
+ temp1[23] = temp0[23];
+ temp1[24] = temp0[24];
+ temp1[25] = temp0[25];
+ temp1[26] = temp0[26];
+ temp1[27] = temp0[27];
+ temp1[28] = temp0[28];
+ temp1[29] = temp0[29];
+ temp1[30] = temp0[30];
+ temp1[31] = temp0[31];
+ temp1[32] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[63], temp0[32], cospi[1], temp0[63], cosBit);
+ temp1[33] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[31], temp0[33], cospi[33], temp0[62], cosBit);
+ temp1[34] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[47], temp0[34], cospi[17], temp0[61], cosBit);
+ temp1[35] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[15], temp0[35], cospi[49], temp0[60], cosBit);
+ temp1[36] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[55], temp0[36], cospi[9], temp0[59], cosBit);
+ temp1[37] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[23], temp0[37], cospi[41], temp0[58], cosBit);
+ temp1[38] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[39], temp0[38], cospi[25], temp0[57], cosBit);
+ temp1[39] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[7], temp0[39], cospi[57], temp0[56], cosBit);
+ temp1[40] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[59], temp0[40], cospi[5], temp0[55], cosBit);
+ temp1[41] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[27], temp0[41], cospi[37], temp0[54], cosBit);
+ temp1[42] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[43], temp0[42], cospi[21], temp0[53], cosBit);
+ temp1[43] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[11], temp0[43], cospi[53], temp0[52], cosBit);
+ temp1[44] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[51], temp0[44], cospi[13], temp0[51], cosBit);
+ temp1[45] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[19], temp0[45], cospi[45], temp0[50], cosBit);
+ temp1[46] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[35], temp0[46], cospi[29], temp0[49], cosBit);
+ temp1[47] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[3], temp0[47], cospi[61], temp0[48], cosBit);
+ temp1[48] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[3], temp0[48], -cospi[61], temp0[47], cosBit);
+ temp1[49] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[35], temp0[49], -cospi[29], temp0[46], cosBit);
+ temp1[50] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[19], temp0[50], -cospi[45], temp0[45], cosBit);
+ temp1[51] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[51], temp0[51], -cospi[13], temp0[44], cosBit);
+ temp1[52] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[11], temp0[52], -cospi[53], temp0[43], cosBit);
+ temp1[53] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[43], temp0[53], -cospi[21], temp0[42], cosBit);
+ temp1[54] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[27], temp0[54], -cospi[37], temp0[41], cosBit);
+ temp1[55] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[59], temp0[55], -cospi[5], temp0[40], cosBit);
+ temp1[56] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[7], temp0[56], -cospi[57], temp0[39], cosBit);
+ temp1[57] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[39], temp0[57], -cospi[25], temp0[38], cosBit);
+ temp1[58] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[23], temp0[58], -cospi[41], temp0[37], cosBit);
+ temp1[59] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[55], temp0[59], -cospi[9], temp0[36], cosBit);
+ temp1[60] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[15], temp0[60], -cospi[49], temp0[35], cosBit);
+ temp1[61] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[47], temp0[61], -cospi[17], temp0[34], cosBit);
+ temp1[62] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[31], temp0[62], -cospi[33], temp0[33], cosBit);
+ temp1[63] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[63], temp0[63], -cospi[1], temp0[32], cosBit);
+
+ // stage 11
+ Unsafe.Add(ref output, 0) = temp1[0];
+ Unsafe.Add(ref output, 1) = temp1[32];
+ Unsafe.Add(ref output, 2) = temp1[16];
+ Unsafe.Add(ref output, 3) = temp1[48];
+ Unsafe.Add(ref output, 4) = temp1[8];
+ Unsafe.Add(ref output, 5) = temp1[40];
+ Unsafe.Add(ref output, 6) = temp1[24];
+ Unsafe.Add(ref output, 7) = temp1[56];
+ Unsafe.Add(ref output, 8) = temp1[4];
+ Unsafe.Add(ref output, 9) = temp1[36];
+ Unsafe.Add(ref output, 10) = temp1[20];
+ Unsafe.Add(ref output, 11) = temp1[52];
+ Unsafe.Add(ref output, 12) = temp1[12];
+ Unsafe.Add(ref output, 13) = temp1[44];
+ Unsafe.Add(ref output, 14) = temp1[28];
+ Unsafe.Add(ref output, 15) = temp1[60];
+ Unsafe.Add(ref output, 16) = temp1[2];
+ Unsafe.Add(ref output, 17) = temp1[34];
+ Unsafe.Add(ref output, 18) = temp1[18];
+ Unsafe.Add(ref output, 19) = temp1[50];
+ Unsafe.Add(ref output, 20) = temp1[10];
+ Unsafe.Add(ref output, 21) = temp1[42];
+ Unsafe.Add(ref output, 22) = temp1[26];
+ Unsafe.Add(ref output, 23) = temp1[58];
+ Unsafe.Add(ref output, 24) = temp1[6];
+ Unsafe.Add(ref output, 25) = temp1[38];
+ Unsafe.Add(ref output, 26) = temp1[22];
+ Unsafe.Add(ref output, 27) = temp1[54];
+ Unsafe.Add(ref output, 28) = temp1[14];
+ Unsafe.Add(ref output, 29) = temp1[46];
+ Unsafe.Add(ref output, 30) = temp1[30];
+ Unsafe.Add(ref output, 31) = temp1[62];
+ Unsafe.Add(ref output, 32) = temp1[1];
+ Unsafe.Add(ref output, 33) = temp1[33];
+ Unsafe.Add(ref output, 34) = temp1[17];
+ Unsafe.Add(ref output, 35) = temp1[49];
+ Unsafe.Add(ref output, 36) = temp1[9];
+ Unsafe.Add(ref output, 37) = temp1[41];
+ Unsafe.Add(ref output, 38) = temp1[25];
+ Unsafe.Add(ref output, 39) = temp1[57];
+ Unsafe.Add(ref output, 40) = temp1[5];
+ Unsafe.Add(ref output, 41) = temp1[37];
+ Unsafe.Add(ref output, 42) = temp1[21];
+ Unsafe.Add(ref output, 43) = temp1[53];
+ Unsafe.Add(ref output, 44) = temp1[13];
+ Unsafe.Add(ref output, 45) = temp1[45];
+ Unsafe.Add(ref output, 46) = temp1[29];
+ Unsafe.Add(ref output, 47) = temp1[61];
+ Unsafe.Add(ref output, 48) = temp1[3];
+ Unsafe.Add(ref output, 49) = temp1[35];
+ Unsafe.Add(ref output, 50) = temp1[19];
+ Unsafe.Add(ref output, 51) = temp1[51];
+ Unsafe.Add(ref output, 52) = temp1[11];
+ Unsafe.Add(ref output, 53) = temp1[43];
+ Unsafe.Add(ref output, 54) = temp1[27];
+ Unsafe.Add(ref output, 55) = temp1[59];
+ Unsafe.Add(ref output, 56) = temp1[7];
+ Unsafe.Add(ref output, 57) = temp1[39];
+ Unsafe.Add(ref output, 58) = temp1[23];
+ Unsafe.Add(ref output, 59) = temp1[55];
+ Unsafe.Add(ref output, 60) = temp1[15];
+ Unsafe.Add(ref output, 61) = temp1[47];
+ Unsafe.Add(ref output, 62) = temp1[31];
+ Unsafe.Add(ref output, 63) = temp1[63];
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs
new file mode 100644
index 0000000000..923227e57f
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+
+internal class Av1Dct8Forward1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 8, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 8, nameof(output));
+ TransformScalar(ref input[0], ref output[0], cosBit);
+ }
+
+ private static void TransformScalar(ref int input, ref int output, int cosBit)
+ {
+ Span temp0 = stackalloc int[8];
+ Span temp1 = stackalloc int[8];
+
+ // stage 0;
+
+ // stage 1;
+ temp0[0] = Unsafe.Add(ref input, 0) + Unsafe.Add(ref input, 7);
+ temp0[1] = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 6);
+ temp0[2] = Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 5);
+ temp0[3] = Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 4);
+ temp0[4] = -Unsafe.Add(ref input, 4) + Unsafe.Add(ref input, 3);
+ temp0[5] = -Unsafe.Add(ref input, 5) + Unsafe.Add(ref input, 2);
+ temp0[6] = -Unsafe.Add(ref input, 6) + Unsafe.Add(ref input, 1);
+ temp0[7] = -Unsafe.Add(ref input, 7) + Unsafe.Add(ref input, 0);
+
+ // stage 2
+ Span cospi = Av1SinusConstants.CosinusPi(cosBit);
+ temp1[0] = temp0[0] + temp0[3];
+ temp1[1] = temp0[1] + temp0[2];
+ temp1[2] = -temp0[2] + temp0[1];
+ temp1[3] = -temp0[3] + temp0[0];
+ temp1[4] = temp0[4];
+ temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[5], cospi[32], temp0[6], cosBit);
+ temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[6], cospi[32], temp0[5], cosBit);
+ temp1[7] = temp0[7];
+
+ // stage 3
+ temp0[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[0], cospi[32], temp1[1], cosBit);
+ temp0[1] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[1], cospi[32], temp1[0], cosBit);
+ temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[2], cospi[16], temp1[3], cosBit);
+ temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[3], -cospi[16], temp1[2], cosBit);
+ temp0[4] = temp1[4] + temp1[5];
+ temp0[5] = -temp1[5] + temp1[4];
+ temp0[6] = -temp1[6] + temp1[7];
+ temp0[7] = temp1[7] + temp1[6];
+
+ // stage 4
+ temp1[0] = temp0[0];
+ temp1[1] = temp0[1];
+ temp1[2] = temp0[2];
+ temp1[3] = temp0[3];
+ temp1[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[4], cospi[8], temp0[7], cosBit);
+ temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[5], cospi[40], temp0[6], cosBit);
+ temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[6], -cospi[40], temp0[5], cosBit);
+ temp1[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[7], -cospi[8], temp0[4], cosBit);
+
+ // stage 5
+ Unsafe.Add(ref output, 0) = temp1[0];
+ Unsafe.Add(ref output, 1) = temp1[4];
+ Unsafe.Add(ref output, 2) = temp1[2];
+ Unsafe.Add(ref output, 3) = temp1[6];
+ Unsafe.Add(ref output, 4) = temp1[1];
+ Unsafe.Add(ref output, 5) = temp1[5];
+ Unsafe.Add(ref output, 6) = temp1[3];
+ Unsafe.Add(ref output, 7) = temp1[7];
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1DctDct4Forward2dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1DctDct4Forward2dTransformer.cs
new file mode 100644
index 0000000000..9442618d20
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1DctDct4Forward2dTransformer.cs
@@ -0,0 +1,100 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+
+internal class Av1DctDct4Forward2dTransformer : Av1Forward2dTransformerBase
+{
+ private readonly Av1Transform2dFlipConfiguration config = new(Av1TransformType.DctDct, Av1TransformSize.Size4x4);
+ private readonly Av1Dct4Forward1dTransformer transformer = new();
+ private readonly int[] temp = new int[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize];
+
+ public void Transform(Span input, Span output, int cosBit, int columnNumber)
+ {
+ /*if (Vector256.IsHardwareAccelerated)
+ {
+ Span> inputVectors = stackalloc Vector128[16];
+ ref Vector128 outputAsVector = ref Unsafe.As>(ref output);
+ TransformVector(ref inputVectors[0], ref outputAsVector, cosBit, columnNumber);
+ }
+ else*/
+ {
+ Transform2dCore(this.transformer, this.transformer, input, 4, output, this.config, this.temp, 8);
+ }
+ }
+
+ ///
+ /// SVT: fdct4x4_sse4_1
+ ///
+ private static void TransformVector(ref Vector128 input, ref Vector128 output, int cosBit, int columnNumber)
+ {
+ // We only use stage-2 bit;
+ // shift[0] is used in load_buffer_4x4()
+ // shift[1] is used in txfm_func_col()
+ // shift[2] is used in txfm_func_row()
+ Span cospi = Av1SinusConstants.CosinusPi(cosBit);
+ Vector128 cospi32 = Vector128.Create(cospi[32]);
+ Vector128 cospi48 = Vector128.Create(cospi[48]);
+ Vector128 cospi16 = Vector128.Create(cospi[16]);
+ Vector128 rnding = Vector128.Create(1 << (cosBit - 1));
+ Vector128 s0, s1, s2, s3;
+ Vector128 u0, u1, u2, u3;
+ Vector128 v0, v1, v2, v3;
+ Vector256