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