diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
index 6539012e5b..089415d25c 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
@@ -160,6 +160,8 @@ internal static class Av1Constants
public const int TransformPadTop = 2;
+ public const int TransformPadBottom = 4;
+
public const int BaseRangeSizeMinus1 = 3;
public const int MaxBaseRange = 15;
@@ -182,4 +184,7 @@ internal static class Av1Constants
public const int MaxTransformStageNumber = 12;
public const int PartitionProbabilitySet = 4;
+
+ // Number of transform sizes that use extended transforms.
+ public const int ExtendedTransformCount = 4;
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
index 62d6d63c08..f390cbdb8b 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
@@ -2,6 +2,8 @@
// Licensed under the Six Labors Split License.
using System;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
@@ -50,6 +52,29 @@ internal static class Av1Math
return result;
}
+ ///
+ /// Long Log 2
+ /// This is a quick adaptation of a Number
+ /// Leading Zeros(NLZ) algorithm to get the log2f of a 32-bit number
+ ///
+ internal static uint Log2_32(uint x)
+ {
+ uint log = 0;
+ int i;
+ for (i = 4; i >= 0; --i)
+ {
+ uint shift = 1u << i;
+ uint n = x >> (int)shift;
+ if (n != 0)
+ {
+ x = n;
+ log += shift;
+ }
+ }
+
+ return log;
+ }
+
public static uint FloorLog2(uint value)
{
uint s = 0;
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
index 6ac11106bf..a7c14a0e59 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
@@ -224,7 +224,7 @@ internal class Av1PredictionDecoder
}
Av1FilterIntraMode filterIntraMode = (plane == Av1Plane.Y && modeInfo.FilterIntraModeInfo.UseFilterIntra)
- ? modeInfo.FilterIntraModeInfo.Mode : Av1FilterIntraMode.FilterIntraModes;
+ ? modeInfo.FilterIntraModeInfo.Mode : Av1FilterIntraMode.AllFilterIntraModes;
int angleDelta = modeInfo.AngleDelta[Math.Min(1, (int)plane)];
@@ -584,7 +584,7 @@ internal class Av1PredictionDecoder
bool needAbove = (need & Av1NeighborNeed.Above) == Av1NeighborNeed.Above;
bool needAboveLeft = (need & Av1NeighborNeed.AboveLeft) == Av1NeighborNeed.AboveLeft;
int angle = 0;
- bool useFilterIntra = filterIntraMode != Av1FilterIntraMode.FilterIntraModes;
+ bool useFilterIntra = filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes;
if (isDirectionalMode)
{
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs
new file mode 100644
index 0000000000..104099eea8
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs
@@ -0,0 +1,24 @@
+// 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 partial class Av1TileWriter
+{
+ internal class Av1BlockGeometry
+ {
+ public Av1BlockSize BlockSize { get; internal set; }
+
+ public Point Origin { get; internal set; }
+
+ public bool HasUv { get; internal set; }
+
+ public int BlockWidth { get; internal set; }
+
+ public int BlockHeight { get; internal set; }
+
+ public required Av1TransformSize[] TransformSize { get; internal set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs
new file mode 100644
index 0000000000..a8113a7ece
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs
@@ -0,0 +1,30 @@
+// 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 partial class Av1TileWriter
+{
+ internal class Av1BlockModeInfoEncoder
+ {
+ public Av1BlockSize BlockSize { get; }
+
+ public Av1PredictionMode PredictionMode { get; }
+
+ public Av1PartitionType PartitionType { get; }
+
+ public Av1PredictionMode UvPredictionMode { get; }
+
+ public bool Skip { get; } = true;
+
+ public bool SkipMode { get; } = true;
+
+ public bool UseIntraBlockCopy { get; } = true;
+
+ public int SegmentId { get; }
+
+ public int TransformDepth { get; internal set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs
new file mode 100644
index 0000000000..53ee7ef8bb
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs
@@ -0,0 +1,8 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal class Av1BlockStruct
+{
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs
new file mode 100644
index 0000000000..c3c29deae4
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs
@@ -0,0 +1,22 @@
+// 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 partial class Av1TileWriter
+{
+ internal class Av1Common
+ {
+ public int ModeInfoRowCount { get; internal set; }
+
+ public int ModeInfoColumnCount { get; internal set; }
+
+ public int ModeInfoStride { get; internal set; }
+
+ public required ObuFrameSize FrameSize { get; internal set; }
+
+ public required ObuTileGroupHeader TilesInfo { get; internal set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs
new file mode 100644
index 0000000000..b8546e72bf
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.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 Av1ComponentType
+{
+ Luminance = 0, // luma
+ Chroma = 1, // chroma (Cb+Cr)
+ ChromaCb = 2, // chroma Cb
+ ChromaCr = 3, // chroma Cr
+ All = 4, // Y+Cb+Cr
+ None = 15
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs
index b2c5259e10..49d93ba24b 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs
@@ -2006,6 +2006,196 @@ internal static class Av1DefaultDistributions
new(14738, 21678, 25779, 27901, 29024, 30302, 30980, 31843, 32144, 32413, 32520, 32594, 32622, 32656, 32660)
];
+ public static Av1Distribution[][][] IntraExtendedTransform =>
+ [
+ [
+ [
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0)
+ ],
+ [
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0)
+ ],
+ [
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0)
+ ],
+ [
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0),
+ new(0)
+ ],
+ ],
+ [
+ [
+ new(1535, 8035, 9461, 12751, 23467, 27825),
+ new(564, 3335, 9709, 10870, 18143, 28094),
+ new(672, 3247, 3676, 11982, 19415, 23127),
+ new(5279, 13885, 15487, 18044, 23527, 30252),
+ new(4423, 6074, 7985, 10416, 25693, 29298),
+ new(1486, 4241, 9460, 10662, 16456, 27694),
+ new(439, 2838, 3522, 6737, 18058, 23754),
+ new(1190, 4233, 4855, 11670, 20281, 24377),
+ new(1045, 4312, 8647, 10159, 18644, 29335),
+ new(202, 3734, 4747, 7298, 17127, 24016),
+ new(447, 4312, 6819, 8884, 16010, 23858),
+ new(277, 4369, 5255, 8905, 16465, 22271),
+ new(3409, 5436, 10599, 15599, 19687, 24040)
+ ],
+ [
+ new(1870, 13742, 14530, 16498, 23770, 27698),
+ new(326, 8796, 14632, 15079, 19272, 27486),
+ new(484, 7576, 7712, 14443, 19159, 22591),
+ new(1126, 15340, 15895, 17023, 20896, 30279),
+ new(655, 4854, 5249, 5913, 22099, 27138),
+ new(1299, 6458, 8885, 9290, 14851, 25497),
+ new(311, 5295, 5552, 6885, 16107, 22672),
+ new(883, 8059, 8270, 11258, 17289, 21549),
+ new(741, 7580, 9318, 10345, 16688, 29046),
+ new(110, 7406, 7915, 9195, 16041, 23329),
+ new(363, 7974, 9357, 10673, 15629, 24474),
+ new(153, 7647, 8112, 9936, 15307, 19996),
+ new(3511, 6332, 11165, 15335, 19323, 23594)
+ ],
+ [
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087)
+ ],
+ [
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ new(4681, 9362, 14043, 18725, 23406, 28087),
+ ],
+ ],
+ [
+ [
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214)
+ ],
+ [
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214)
+ ],
+ [
+ new(1127, 12814, 22772, 27483),
+ new(145, 6761, 11980, 26667),
+ new(362, 5887, 11678, 16725),
+ new(385, 15213, 18587, 30693),
+ new(25, 2914, 23134, 27903),
+ new(60, 4470, 11749, 23991),
+ new(37, 3332, 14511, 21448),
+ new(157, 6320, 13036, 17439),
+ new(119, 6719, 12906, 29396),
+ new(47, 5537, 12576, 21499),
+ new(269, 6076, 11258, 23115),
+ new(83, 5615, 12001, 17228),
+ new(1968, 5556, 12023, 18547)
+ ],
+ [
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214),
+ new(6554, 13107, 19661, 26214)
+ ]
+ ],
+ ];
+
public static Av1Distribution[][][] GetEndOfBlockFlag(int baseQIndex)
{
int qContext = GetQContext(baseQIndex);
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs
new file mode 100644
index 0000000000..61ebeb364f
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal partial class Av1TileWriter
+{
+ internal class Av1EncoderBlockStruct
+ {
+ public required Av1MacroBlockD av1xd { get; internal set; }
+
+ public required int[] PaletteSize { get; internal set; }
+
+ public int QIndex { get; internal set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs
new file mode 100644
index 0000000000..05c501d4d9
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal partial class Av1TileWriter
+{
+ internal class Av1EntropyCodingContext
+ {
+ public required Av1MacroBlockModeInfo MacroBlockModeInfo { get; internal set; }
+
+ public Point SuperblockOrigin { get; internal set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs
index eaf33a3d8c..f536982e01 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs
@@ -10,5 +10,5 @@ internal enum Av1FilterIntraMode
Horizontal,
Directional157,
Paeth,
- FilterIntraModes,
+ AllFilterIntraModes,
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs
new file mode 100644
index 0000000000..0a2e2e8325
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs
@@ -0,0 +1,15 @@
+// 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 static class Av1FilterIntraModeExtensions
+{
+ private static readonly Av1PredictionMode[] IntraDirection =
+ [Av1PredictionMode.DC, Av1PredictionMode.Vertical, Av1PredictionMode.Horizontal, Av1PredictionMode.Directional157Degrees, Av1PredictionMode.DC];
+
+ public static Av1PredictionMode ToIntraDirection(this Av1FilterIntraMode mode)
+ => IntraDirection[(int)mode];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs
new file mode 100644
index 0000000000..da1469173b
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal partial class Av1TileWriter
+{
+ internal class Av1MacroBlockD
+ {
+ public required Av1BlockModeInfo ModeInfo { get; internal set; }
+
+ public required Av1TileInfo Tile { get; internal set; }
+
+ public bool IsUpAvailable { get; }
+
+ public bool IsLeftAvailable { get; }
+
+ public Av1MacroBlockModeInfo? AboveMacroBlock { get; internal set; }
+
+ public Av1MacroBlockModeInfo? LeftMacroBlock { get; internal set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs
new file mode 100644
index 0000000000..8fb96f4d2d
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal partial class Av1TileWriter
+{
+ internal class Av1MacroBlockModeInfo
+ {
+ public required Av1BlockModeInfoEncoder Block { get; internal set; }
+
+ public required Av1PaletteLumaModeInfo Palette { get; internal set; }
+
+ public int CdefStrength { get; internal set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs
new file mode 100644
index 0000000000..0aa6a68b09
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal partial class Av1TileWriter
+{
+ internal class Av1NeighborArrayUnit
+ {
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs
index c5505899ad..89fab2b84b 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs
@@ -282,7 +282,7 @@ internal static class Av1NzMap
NzMapContextOffset64x32, // TX_64x16
];
- public static int GetNzMagnitude(Span levels, int bwl, Av1TransformClass transformClass)
+ public static int GetNzMagnitude(ReadOnlySpan levels, int bwl, Av1TransformClass transformClass)
{
int mag;
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs
new file mode 100644
index 0000000000..bb40ca53a8
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal partial class Av1TileWriter
+{
+ internal class Av1PaletteLumaModeInfo
+ {
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs
new file mode 100644
index 0000000000..851f71dae3
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal partial class Av1TileWriter
+{
+ internal class Av1PictureControlSet
+ {
+ public required Av1NeighborArrayUnit[] luma_dc_sign_level_coeff_na { get; internal set; }
+
+ public required Av1NeighborArrayUnit[] cr_dc_sign_level_coeff_na { get; internal set; }
+
+ public required Av1NeighborArrayUnit[] cb_dc_sign_level_coeff_na { get; internal set; }
+
+ public required Av1NeighborArrayUnit[] txfm_context_array { get; internal set; }
+
+ public required Av1SequenceControlSet Sequence { get; internal set; }
+
+ public required Av1PictureParentControlSet Parent { get; internal set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs
new file mode 100644
index 0000000000..6bbc651eac
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs
@@ -0,0 +1,20 @@
+// 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 partial class Av1TileWriter
+{
+ internal class Av1PictureParentControlSet
+ {
+ public required Av1Common Common { get; internal set; }
+
+ public required ObuFrameHeader FrameHeader { get; internal set; }
+
+ public required int[] PreviousQIndex { get; internal set; }
+
+ public int PaletteLevel { get; internal set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs
new file mode 100644
index 0000000000..1ad59b31ab
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs
@@ -0,0 +1,14 @@
+// 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 partial class Av1TileWriter
+{
+ internal class Av1SequenceControlSet
+ {
+ public required ObuSequenceHeader SequenceHeader { get; internal set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs
new file mode 100644
index 0000000000..776451aeaf
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
+
+internal partial class Av1TileWriter
+{
+ internal class Av1Superblock
+ {
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs
index a327d7094a..f116abe8df 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs
@@ -10,8 +10,8 @@ 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 static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
- private static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513];
+ public static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+ public static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513];
private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes;
@@ -233,7 +233,7 @@ internal ref struct Av1SymbolDecoder
return RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra);
}
- public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
+ public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{
int i = endOfBlock - 1;
int pos = scan[i];
@@ -253,10 +253,10 @@ internal ref struct Av1SymbolDecoder
}
}
- levels[GetPaddedIndex(pos, bwl)] = level;
+ levels[GetPaddedIndex(pos, bwl)] = (byte)level;
}
- public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
+ public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{
for (int c = endScanIndex; c >= startScanIndex; --c)
{
@@ -277,11 +277,11 @@ internal ref struct Av1SymbolDecoder
}
}
- levels[GetPaddedIndex(pos, bwl)] = level;
+ levels[GetPaddedIndex(pos, bwl)] = (byte)level;
}
}
- public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
+ public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{
for (int c = endScanIndex; c >= startScanIndex; --c)
{
@@ -302,11 +302,11 @@ internal ref struct Av1SymbolDecoder
}
}
- levels[GetPaddedIndex(pos, bwl)] = level;
+ levels[GetPaddedIndex(pos, bwl)] = (byte)level;
}
}
- public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int bwl, Span levels, int dcSignContext, Av1PlaneType planeType)
+ public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int bwl, Span levels, int dcSignContext, Av1PlaneType planeType)
{
int maxScanLine = 0;
int culLevel = 0;
@@ -439,7 +439,7 @@ internal ref struct Av1SymbolDecoder
return 3;
}
- private static int GetBaseRangeContext2d(Span levels, int c, int bwl)
+ private static int GetBaseRangeContext2d(Span levels, int c, int bwl)
{
DebugGuard.MustBeGreaterThan(c, 0, nameof(c));
int row = c >> bwl;
@@ -447,9 +447,9 @@ internal ref struct Av1SymbolDecoder
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);
+ Math.Min((int)levels[pos + 1], Av1Constants.MaxBaseRange) +
+ Math.Min((int)levels[pos + stride], Av1Constants.MaxBaseRange) +
+ Math.Min((int)levels[pos + 1 + stride], Av1Constants.MaxBaseRange);
mag = Math.Min((mag + 1) >> 1, 6);
if ((row | col) < 2)
{
@@ -459,22 +459,22 @@ internal ref struct Av1SymbolDecoder
return mag + 14;
}
- private static int GetLowerLevelsContext2d(Span levels, int pos, int bwl, Av1TransformSize transformSize)
+ 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 }
+ mag = Math.Min((int)levels[1], 3); // { 0, 1 }
+ mag += Math.Min((int)levels[(1 << bwl) + Av1Constants.TransformPadHorizontal], 3); // { 1, 0 }
+ mag += Math.Min((int)levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1], 3); // { 1, 1 }
+ mag += Math.Min((int)levels[2], 3); // { 0, 2 }
+ mag += Math.Min((int)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 int GetBaseRangeContext(Span levels, int c, int bwl, Av1TransformClass transformClass)
+ private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1TransformClass transformClass)
{
int row = c >> bwl;
int col = c - (row << bwl);
@@ -533,13 +533,13 @@ internal ref struct Av1SymbolDecoder
return mag + 14;
}
- private static int GetLowerLevelsContext(Span levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass)
+ private static int GetLowerLevelsContext(ReadOnlySpan 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 GetPaddedIndex(int scanIndex, int bwl)
+ public static int GetPaddedIndex(int scanIndex, int bwl)
=> scanIndex + ((scanIndex >> bwl) << Av1Constants.TransformPadHorizontalLog2);
private int ReadGolomb()
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs
index 78bc2eec49..64664f1c1b 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs
@@ -1,20 +1,90 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System;
using System.Buffers;
+using System.Drawing;
+using System.Formats.Asn1;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
internal class Av1SymbolEncoder : IDisposable
{
+ private static readonly int[][] ExtendedTransformIndices = [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [1, 3, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [1, 5, 6, 4, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0],
+ [3, 4, 5, 8, 6, 7, 9, 10, 11, 0, 1, 2, 0, 0, 0, 0],
+ [7, 8, 9, 12, 10, 11, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6],
+ ];
+
+ private static readonly byte[] EndOfBlockToPositionSmall = [
+ 0, 1, 2, // 0-2
+ 3, 3, // 3-4
+ 4, 4, 4, 4, // 5-8
+ 5, 5, 5, 5, 5, 5, 5, 5, // 9-16
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 // 17-32
+ ];
+
+ private static readonly byte[] EndOfBlockToPositionLarge = [
+ 6, // place holder
+ 7, // 33-64
+ 8,
+ 8, // 65-128
+ 9,
+ 9,
+ 9,
+ 9, // 129-256
+ 10,
+ 10,
+ 10,
+ 10,
+ 10,
+ 10,
+ 10,
+ 10, // 257-512
+ 11 // 513-
+ ];
+
+ private static readonly int[] TransformCountInSet = [1, 2, 5, 7, 12, 16];
+
+ // Maps tx set types to the indices.
+ private static readonly int[][] ExtendedTransformSetToIndex = [
+
+ // Intra
+ [0, -1, 2, 1, -1, -1],
+
+ // Inter
+ [0, 3, -1, -1, 2, 1]
+ ];
+
private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes;
-
+ private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip;
+ private readonly Av1Distribution[][][] endOfBlockFlag;
+ private readonly Av1Distribution[][][] coefficientsBaseRange;
+ private readonly Av1Distribution[][][] coefficientsBase;
+ private readonly Av1Distribution[][][] coefficientsBaseEndOfBlock;
+ private readonly Av1Distribution[][] dcSign;
+ private readonly Av1Distribution[][][] endOfBlockExtra;
+ private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform;
private bool isDisposed;
private Av1SymbolWriter writer;
- public Av1SymbolEncoder(Configuration configuration, int initialSize)
- => this.writer = new(configuration, initialSize);
+ public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex)
+ {
+ this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex);
+ this.coefficientsBaseRange = Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex);
+ this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex);
+ this.coefficientsBaseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex);
+ this.dcSign = Av1DefaultDistributions.GetDcSign(qIndex);
+ this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex);
+ this.writer = new(configuration, initialSize);
+ }
public void WriteUseIntraBlockCopy(bool value)
{
@@ -44,6 +114,332 @@ internal class Av1SymbolEncoder : IDisposable
w.WriteSymbol(value, distribution);
}
+ ///
+ /// SVT: av1_write_coeffs_txb_1d
+ ///
+ public int WriteCoefficients(
+ Av1TransformSize transformSize,
+ Av1TransformType transformType,
+ uint txb_index, // TODO: Doesn't seem to be used, remove.
+ Av1PredictionMode intraLumaDir,
+ Span coeff_buffer_ptr,
+ Av1ComponentType componentType,
+ short txb_skip_ctx,
+ short dc_sign_ctx,
+ ushort eob,
+ bool useReducedTransformSet,
+ int baseQIndex,
+ Av1FilterIntraMode filterIntraMode)
+ {
+ int c;
+ int width = transformSize.GetWidth();
+ int height = transformSize.GetHeight();
+ Av1TransformClass transformClass = transformType.ToClass();
+ Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType);
+ ReadOnlySpan scan = scanOrder.Scan;
+ int bwl = transformSize.GetBlockWidthLog2();
+ Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1) >> 1);
+
+ ref Av1SymbolWriter w = ref this.writer;
+
+ Span levels_buf = new byte[Av1Constants.TransformPad2d];
+ Span levels = SetLevels(levels_buf, width);
+ Span coeff_contexts = new sbyte[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize];
+
+ Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext));
+
+ bool hasEndOfBlock = eob != 0;
+ this.WriteSkip(!hasEndOfBlock, txb_skip_ctx);
+
+ if (eob == 0)
+ {
+ return 0;
+ }
+
+ InitializeLevels(coeff_buffer_ptr, width, height, levels);
+ if (componentType == Av1ComponentType.Luminance)
+ {
+ this.WriteTransformType(transformType, transformSize, useReducedTransformSet, baseQIndex, filterIntraMode, intraLumaDir);
+ }
+
+ short endOfBlockPosition = GetEndOfBlockPosition(eob, out int eob_extra);
+ this.WriteEndOfBlockFlag(componentType, transformClass, transformSize, endOfBlockPosition);
+
+ int eob_offset_bits = Av1SymbolDecoder.EndOfBlockOffsetBits[endOfBlockPosition];
+ if (eob_offset_bits > 0)
+ {
+ int eob_shift = eob_offset_bits - 1;
+ int bit = Math.Max(1, eob_extra & (1 << eob_shift));
+ w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockPosition]);
+ for (int i = 1; i < eob_offset_bits; i++)
+ {
+ eob_shift = eob_offset_bits - 1 - i;
+ bit = Math.Max(1, eob_extra & (1 << eob_shift));
+ w.WriteLiteral((uint)bit, 1);
+ }
+ }
+
+ GetNzMapContexts(levels, scan, eob, transformSize, transformClass, coeff_contexts);
+ int limitedTransformSizeContext = Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32);
+ for (c = eob - 1; c >= 0; --c)
+ {
+ short pos = scan[c];
+ int v = coeff_buffer_ptr[pos];
+ short coeff_ctx = coeff_contexts[pos];
+ int level = Math.Abs(v);
+
+ if (c == eob - 1)
+ {
+ w.WriteSymbol(Math.Min(level, 3) - 1, this.coefficientsBaseEndOfBlock[(int)transformSizeContext][(int)componentType][coeff_ctx]);
+ }
+ else
+ {
+ w.WriteSymbol(Math.Min(level, 3), this.coefficientsBase[(int)transformSizeContext][(int)componentType][coeff_ctx]);
+ }
+
+ if (level > Av1Constants.BaseLevelsCount)
+ {
+ // level is above 1.
+ int baseRange = level - 1 - Av1Constants.BaseLevelsCount;
+ int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass);
+ for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1)
+ {
+ int k = Math.Min(baseRange - idx, Av1Constants.BaseRangeSizeMinus1);
+ w.WriteSymbol(k, this.coefficientsBaseRange[limitedTransformSizeContext][(int)componentType][baseRangeContext]);
+ if (k < Av1Constants.BaseRangeSizeMinus1)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ // Loop to code all signs in the transform block,
+ // starting with the sign of DC (if applicable)
+ int cul_level = 0;
+ for (c = 0; c < eob; ++c)
+ {
+ short pos = scan[c];
+ int v = coeff_buffer_ptr[pos];
+ int level = Math.Abs(v);
+ cul_level += level;
+
+ uint sign = (v < 0) ? 1u : 0u;
+ if (level > 0)
+ {
+ if (c == 0)
+ {
+ w.WriteSymbol((int)sign, this.dcSign[(int)componentType][dc_sign_ctx]);
+ }
+ else
+ {
+ w.WriteLiteral(sign, 1);
+ }
+
+ if (level > Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount)
+ {
+ this.WriteGolomb(level - Av1Constants.CoefficientBaseRange - 1 - Av1Constants.BaseLevelsCount);
+ }
+ }
+ }
+
+ cul_level = Math.Min(Av1Constants.CoefficientContextMask, cul_level);
+
+ // DC value
+ SetDcSign(ref cul_level, coeff_buffer_ptr[0]);
+ return cul_level;
+ }
+
+ ///
+ /// SVT: set_dc_sign
+ ///
+ private static void SetDcSign(ref int cul_level, int dc_val)
+ {
+ if (dc_val < 0)
+ {
+ cul_level |= 1 << Av1Constants.CoefficientContextBitCount;
+ }
+ else if (dc_val > 0)
+ {
+ cul_level += 2 << Av1Constants.CoefficientContextBitCount;
+ }
+ }
+
+ ///
+ /// SVT: get_br_ctx
+ ///
+ private static int GetBaseRangeContext(Span levels, short 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;
+ }
+
+ ///
+ /// SVT: get_eob_pos_token
+ ///
+ private static short GetEndOfBlockPosition(ushort endOfBlock, out int extra)
+ {
+ short t;
+ if (endOfBlock < 33)
+ {
+ t = EndOfBlockToPositionSmall[endOfBlock];
+ }
+ else
+ {
+ int e = Math.Min((endOfBlock - 1) >> 5, 16);
+ t = EndOfBlockToPositionLarge[e];
+ }
+
+ extra = endOfBlock - Av1SymbolDecoder.EndOfBlockGroupStart[t];
+ return t;
+ }
+
+ ///
+ /// SVT: get_nz_map_ctx
+ ///
+ private static sbyte GetNzMapContext(
+ ReadOnlySpan levels,
+ int pos,
+ int bwl,
+ int height,
+ int scan_idx,
+ bool is_eob,
+ Av1TransformSize transformSize,
+ Av1TransformClass transformClass)
+ {
+ if (is_eob)
+ {
+ if (scan_idx == 0)
+ {
+ return 0;
+ }
+
+ if (scan_idx <= (height << bwl) / 8)
+ {
+ return 1;
+ }
+
+ if (scan_idx <= (height << bwl) / 4)
+ {
+ return 2;
+ }
+
+ return 3;
+ }
+
+ int stats = Av1NzMap.GetNzMagnitude(levels[Av1SymbolDecoder.GetPaddedIndex(pos, bwl)..], bwl, transformClass);
+ return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass);
+ }
+
+ ///
+ /// SVT: svt_av1_get_nz_map_contexts_c
+ ///
+ private static void GetNzMapContexts(
+ ReadOnlySpan levels,
+ ReadOnlySpan scan,
+ ushort eob,
+ Av1TransformSize tx_size,
+ Av1TransformClass tx_class,
+ Span coeff_contexts)
+ {
+ int bwl = tx_size.GetBlockWidthLog2();
+ int height = tx_size.GetHeight();
+ for (int i = 0; i < eob; ++i)
+ {
+ int pos = scan[i];
+ coeff_contexts[pos] = GetNzMapContext(levels, pos, bwl, height, i, i == eob - 1, tx_size, tx_class);
+ }
+ }
+
+ ///
+ /// SVT: svt_av1_txb_init_levels_c
+ ///
+ private static void InitializeLevels(Span coefficientBuffer, int width, int height, Span levels)
+ {
+ int stride = width + Av1Constants.TransformPadHorizontal;
+ ref byte ls = ref levels[0];
+
+ Unsafe.InitBlock(ref levels[-Av1Constants.TransformPadTop * stride], 0, (uint)(Av1Constants.TransformPadTop * stride));
+ Unsafe.InitBlock(ref levels[stride * height], 0, (uint)((Av1Constants.TransformPadBottom * stride) + Av1Constants.TransformPadEnd));
+
+ for (int i = 0; i < height; i++)
+ {
+ for (int j = 0; j < width; j++)
+ {
+ ls = (byte)Av1Math.Clamp(Math.Abs(coefficientBuffer[(i * width) + j]), 0, byte.MaxValue);
+ ls = ref Unsafe.Add(ref ls, 1);
+ }
+
+ Unsafe.InitBlock(ref ls, 0, Av1Constants.TransformPadHorizontal);
+ }
+ }
+
+ ///
+ /// SVT: set_levels from EbCommonUtils.h
+ ///
+ private static Span SetLevels(Span levels_buf, int width)
+ => levels_buf.Slice(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal));
+
+ private void WriteSkip(bool hasEndOfBlock, int context)
+ {
+ // Has EOB, means we won't skip, negating the logic.
+ ref Av1SymbolWriter w = ref this.writer;
+ w.WriteSymbol(hasEndOfBlock ? 0 : 1, this.skip[context]);
+ }
+
public IMemoryOwner Exit()
{
ref Av1SymbolWriter w = ref this.writer;
@@ -58,4 +454,137 @@ internal class Av1SymbolEncoder : IDisposable
this.isDisposed = true;
}
}
+
+ ///
+ /// SVT: write_golomb
+ ///
+ private void WriteGolomb(int level)
+ {
+ int x = level + 1;
+ int i = x;
+ int length = (int)Av1Math.Log2_32((uint)x) + 1;
+
+ Guard.MustBeGreaterThan(length, 0, nameof(length));
+
+ ref Av1SymbolWriter w = ref this.writer;
+ for (i = 0; i < length - 1; ++i)
+ {
+ w.WriteLiteral(0, 1);
+ }
+
+ for (int j = length - 1; j >= 0; --j)
+ {
+ w.WriteLiteral((uint)((x >> j) & 0x01), 1);
+ }
+ }
+
+ private void WriteEndOfBlockFlag(Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, int endOfBlockPosition)
+ {
+ int endOfBlockMultiSize = transformSize.GetLog2Minus4();
+ int endOfBlockContext = transformClass == Av1TransformClass.Class2D ? 0 : 1;
+ ref Av1SymbolWriter w = ref this.writer;
+ w.WriteSymbol(endOfBlockPosition - 1, this.endOfBlockFlag[endOfBlockMultiSize][(int)componentType][endOfBlockContext]);
+ }
+
+ ///
+ /// SVT: av1_write_tx_type
+ ///
+ private void WriteTransformType(
+ Av1TransformType transformType,
+ Av1TransformSize transformSize,
+ bool useReducedTransformSet,
+ int baseQIndex,
+ Av1FilterIntraMode filterIntraMode,
+ Av1PredictionMode intraDirection)
+ {
+ ref Av1SymbolWriter w = ref this.writer;
+ bool isInter = false; // mbmi->block_mi.use_intrabc || is_inter_mode(mbmi->block_mi.mode);
+ if (GetExtendedTransformTypeCount(transformSize, isInter, useReducedTransformSet) > 1 && (baseQIndex > 0))
+ {
+ Av1TransformSize square_tx_size = transformSize.GetSquareSize();
+ Guard.MustBeLessThanOrEqualTo((int)square_tx_size, Av1Constants.ExtendedTransformCount, nameof(square_tx_size));
+
+ Av1TransformSetType tx_set_type = GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet);
+ int eset = GetExtendedTransformSet(transformSize, isInter, useReducedTransformSet);
+
+ // eset == 0 should correspond to a set with only DCT_DCT and there
+ // is no need to send the tx_type
+ Guard.MustBeGreaterThan(eset, 0, nameof(eset));
+
+ // assert(av1_ext_tx_used[tx_set_type][transformType]);
+ if (isInter)
+ {
+ /*
+ w.WriteSymbol(
+ av1_ext_tx_ind[tx_set_type][transformType],
+ this.inter_ext_tx_cdf[eset][square_tx_size]);*/
+ }
+ else
+ {
+ Av1PredictionMode intra_dir;
+ if (filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes)
+ {
+ intra_dir = filterIntraMode.ToIntraDirection();
+ }
+ else
+ {
+ intra_dir = intraDirection;
+ }
+
+ Guard.MustBeLessThan((int)intra_dir, 13, nameof(intra_dir));
+ Guard.MustBeLessThan((int)square_tx_size, 4, nameof(square_tx_size));
+ w.WriteSymbol(
+ ExtendedTransformIndices[(int)tx_set_type][(int)transformType],
+ this.intraExtendedTransform[eset][(int)square_tx_size][(int)intra_dir]);
+ }
+ }
+ }
+
+ ///
+ /// SVT: get_ext_tx_set
+ ///
+ private static int GetExtendedTransformSet(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet)
+ {
+ int set_type = (int)GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet);
+ return ExtendedTransformSetToIndex[isInter ? 1 : 0][set_type];
+ }
+
+ ///
+ /// SVT: get_ext_tx_set_type
+ ///
+ private static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet)
+ {
+ Av1TransformSize transformSizeSquareUp = transformSize.GetSquareUpSize();
+
+ if (transformSizeSquareUp > Av1TransformSize.Size32x32)
+ {
+ return Av1TransformSetType.DctOnly;
+ }
+
+ if (transformSizeSquareUp == Av1TransformSize.Size32x32)
+ {
+ return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.DctOnly;
+ }
+
+ if (useReducedTransformSet)
+ {
+ return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.Dtt4Identity;
+ }
+
+ Av1TransformSize transformSizeSquare = transformSize.GetSquareSize();
+ if (isInter)
+ {
+ return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt9Identity1dDct : Av1TransformSetType.All16;
+ }
+ else
+ {
+ return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct;
+ }
+ }
+
+ private static int GetExtendedTransformTypeCount(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet)
+ {
+ int set_type = (int)GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet);
+ return TransformCountInSet[set_type];
+ }
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
index 2d6372ad6c..751f610a5c 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
@@ -564,8 +564,8 @@ internal class Av1TileReader : IAv1TileReader
Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1);
int culLevel = 0;
- int[] levelsBuffer = new int[Av1Constants.TransformPad2d];
- Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..];
+ byte[] levelsBuffer = new byte[Av1Constants.TransformPad2d];
+ Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..];
bool allZero = reader.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext);
int bwl = transformSize.GetBlockWidthLog2();
@@ -590,7 +590,7 @@ internal class Av1TileReader : IAv1TileReader
endOfBlock = reader.ReadEndOfBlockPosition(transformSize, transformClass, transformSizeContext, planeType);
if (endOfBlock > 1)
{
- Array.Fill(levelsBuffer, 0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd);
+ Array.Fill(levelsBuffer, (byte)0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd);
}
reader.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, height, scan, bwl, levels, transformSizeContext, planeType);
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs
index 81f2d84776..327932b24e 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs
@@ -2,17 +2,17 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
-using System.Reflection;
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Memory;
-using static System.Runtime.InteropServices.JavaScript.JSType;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
[Trait("Format", "Avif")]
public class SymbolTests
{
+ private const int baseQIndex = 23;
+
[Fact]
public void ReadRandomLiteral()
{
@@ -194,7 +194,7 @@ public class SymbolTests
// Assign
int ctx = 7;
Configuration configuration = Configuration.Default;
- Av1SymbolEncoder encoder = new(configuration, 100 / 8);
+ Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex);
Av1PartitionType[] values = [
Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None,
Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.None];
@@ -231,7 +231,7 @@ public class SymbolTests
{
// Assign
Configuration configuration = Configuration.Default;
- Av1SymbolEncoder encoder = new(configuration, 100 / 8);
+ Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex);
Av1PartitionType[] values = [
Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Horizontal,
Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Horizontal, Av1PartitionType.Horizontal];
@@ -268,7 +268,7 @@ public class SymbolTests
{
// Assign
Configuration configuration = Configuration.Default;
- Av1SymbolEncoder encoder = new(configuration, 100 / 8);
+ Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex);
Av1PartitionType[] values = [
Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Vertical,
Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Vertical, Av1PartitionType.Vertical];
@@ -299,7 +299,7 @@ public class SymbolTests
// Assign
bool[] values = [true, true, false, true, false, false, false];
Configuration configuration = Configuration.Default;
- Av1SymbolEncoder encoder = new(configuration, 100 / 8);
+ Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex);
bool[] actuals = new bool[values.Length];
// Act