diff --git a/src/ImageSharp/Formats/Jxl/IO/Entropy/JxlAnsLz77Parameters.cs b/src/ImageSharp/Formats/Jxl/IO/Entropy/JxlAnsLz77Parameters.cs index cbf6d3a4e..01016f4dd 100644 --- a/src/ImageSharp/Formats/Jxl/IO/Entropy/JxlAnsLz77Parameters.cs +++ b/src/ImageSharp/Formats/Jxl/IO/Entropy/JxlAnsLz77Parameters.cs @@ -7,15 +7,75 @@ namespace SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; internal sealed class JxlAnsLz77Parameters : IJxlFields { - public bool Enabled { get; set; } + private bool enabled; + private uint minimumSymbol; + private uint minimumLength; + private JxlAnsHybridUIntConfiguration lengthUintConfig = new(0, 0, 0); - public uint MinimumSymbol { get; set; } + public JxlAnsLz77Parameters() => JxlBundle.Init(this); - public uint MinimumLength { get; set; } + public bool Enabled + { + get => this.enabled; + set => this.enabled = value; + } - public JxlAnsHybridUIntConfiguration LengthUintConfig { get; set; } = new(0, 0, 0); + public uint MinimumSymbol + { + get => this.minimumSymbol; + set => this.minimumSymbol = value; + } + + public uint MinimumLength + { + get => this.minimumLength; + set => this.minimumLength = value; + } + + public JxlAnsHybridUIntConfiguration LengthUintConfig + { + get => this.lengthUintConfig; + set => this.lengthUintConfig = value; + } public int NonserializedDistanceContext { get; set; } - public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); + public ref JxlAnsHybridUIntConfiguration GetLengthUIntConfigReference() => ref this.lengthUintConfig; + + public bool Visit(JxlVisitor visitor) + { + if (!visitor.Boolean(false, ref this.enabled)) + { + return false; + } + + if (!visitor.Conditional(this.enabled)) + { + return true; + } + + if (!visitor.U32( + JxlFieldExpressions.Value(224u), + JxlFieldExpressions.Value(512u), + JxlFieldExpressions.Value(4096u), + JxlFieldExpressions.BitsOffset(15u, 8u), + 224u, + ref this.minimumSymbol)) + { + return false; + } + + if (!visitor.U32( + JxlFieldExpressions.Value(3u), + JxlFieldExpressions.Value(4u), + JxlFieldExpressions.BitsOffset(2u, 5u), + JxlFieldExpressions.BitsOffset(8u, 9u), + 3u, + ref this.minimumLength)) + { + return false; + } + + return true; + } } diff --git a/src/ImageSharp/Formats/Jxl/IO/JxlHuffmanCode.cs b/src/ImageSharp/Formats/Jxl/IO/JxlHuffmanCode.cs index 30bca3446..2f95f7226 100644 --- a/src/ImageSharp/Formats/Jxl/IO/JxlHuffmanCode.cs +++ b/src/ImageSharp/Formats/Jxl/IO/JxlHuffmanCode.cs @@ -6,15 +6,15 @@ namespace SixLabors.ImageSharp.Formats.Jxl.IO; /// /// A single Huffman code. /// -internal struct JxlHuffmanCode +internal struct JxlHuffmanCode(byte bits, ushort value) { /// /// Number of bits for this symbol. /// - public byte Bits; + public byte Bits = bits; /// /// Symbol value/offset. /// - public ushort Value; + public ushort Value = value; } diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3B.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3B.cs index 63a5c59c9..8c3b2ae36 100644 --- a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3B.cs +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3B.cs @@ -11,4 +11,9 @@ internal sealed class JxlImage3B : JxlImage3 public JxlImage3B() { } + + public JxlImage3B(Configuration configuration, int xSize, int ySize) + : base(configuration, xSize, ySize) + { + } } diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3F.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3F.cs index b456dfd9a..8387f6319 100644 --- a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3F.cs +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3F.cs @@ -11,4 +11,9 @@ internal sealed class JxlImage3F : JxlImage3 public JxlImage3F() { } + + public JxlImage3F(Configuration configuration, int xSize, int ySize) + : base(configuration, xSize, ySize) + { + } } diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3I.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3I.cs index 0d9f6c820..f3486b95a 100644 --- a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3I.cs +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3I.cs @@ -11,4 +11,9 @@ internal sealed class JxlImage3I : JxlImage3 public JxlImage3I() { } + + public JxlImage3I(Configuration configuration, int xSize, int ySize) + : base(configuration, xSize, ySize) + { + } } diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3S.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3S.cs index 00615ff84..94b88056a 100644 --- a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3S.cs +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3S.cs @@ -11,4 +11,9 @@ internal sealed class JxlImage3S : JxlImage3 public JxlImage3S() { } + + public JxlImage3S(Configuration configuration, int xSize, int ySize) + : base(configuration, xSize, ySize) + { + } } diff --git a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3U.cs b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3U.cs index 1921e86d3..8d118c048 100644 --- a/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3U.cs +++ b/src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3U.cs @@ -11,4 +11,9 @@ internal sealed class JxlImage3U : JxlImage3 public JxlImage3U() { } + + public JxlImage3U(Configuration configuration, int xSize, int ySize) + : base(configuration, xSize, ySize) + { + } } diff --git a/src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs b/src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs index ab637308f..9e9b10c09 100644 --- a/src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs +++ b/src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs @@ -17,6 +17,9 @@ internal class JxlImage3 : IDisposable { } + public JxlImage3(Configuration configuration, int xSize, int ySize) + => this.Allocate(configuration, xSize, ySize); + public JxlImage3(JxlImage3 other) { for (int i = 0; i < PlaneCount; i++) @@ -54,15 +57,15 @@ internal class JxlImage3 : IDisposable } public static JxlImage3 Create(Configuration configuration, int xSize, int ySize) + => new(configuration, xSize, ySize); + + public void Allocate(Configuration configuration, int xSize, int ySize) { JxlPlane plane0 = JxlPlane.Create(configuration, xSize, ySize); JxlPlane plane1 = JxlPlane.Create(configuration, xSize, ySize); JxlPlane plane2 = JxlPlane.Create(configuration, xSize, ySize); - return new JxlImage3() - { - planes = [plane0, plane1, plane2] - }; + this.planes = [plane0, plane1, plane2]; } public bool ShrinkTo(int x, int y) diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsCode.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsCode.cs new file mode 100644 index 000000000..56dbab4ed --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsCode.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; + +internal sealed class JxlAnsCode +{ + public List HuffmanData { get; set; } = []; + + public List UIntConfig { get; set; } = []; + + public List DegenerateSymbols { get; set; } = []; + + public bool UsePrefixCode { get; set; } + + public byte LogAlphaSize { get; set; } + + public JxlAnsLz77Parameters Lz77 { get; set; } = new(); + + public int MaxNumBits { get; set; } + + public void UpdateMaxNumBits(int ctx, int symbol) + { + Span configs = CollectionsMarshal.AsSpan(this.UIntConfig); + ref JxlAnsHybridUIntConfiguration cfg = ref configs[ctx]; + if (this.Lz77.Enabled && this.Lz77.NonserializedDistanceContext != ctx && symbol >= this.Lz77.MinimumSymbol) + { + symbol -= (int)this.Lz77.MinimumSymbol; + cfg = ref this.Lz77.GetLengthUIntConfigReference(); + } + + uint splitToken = cfg.SplitToken; + uint msbInToken = cfg.MsbInToken; + uint lsbInToken = cfg.LsbInToken; + uint splitExponent = cfg.SplitExponent; + + if (symbol < splitToken) + { + this.MaxNumBits = Math.Max(this.MaxNumBits, (int)splitExponent); + return; + } + + uint nExtra = splitExponent - (msbInToken + lsbInToken) + (((uint)symbol - splitToken) >> (int)(msbInToken + lsbInToken)); + uint total = msbInToken + lsbInToken + nExtra + 1; + this.MaxNumBits = Math.Max(this.MaxNumBits, (int)total); + } +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsReader.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsReader.cs index 3795f092b..51e014d80 100644 --- a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsReader.cs +++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsReader.cs @@ -2,12 +2,17 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; internal static class JxlAnsReader { + private const int WindowSize = 1 << 20; + + private const int NumSpecialDistances = 120; + // Prefer jagged arrays over multidimensional arrays // for performance. Collection expressions help represent // jagged arrays easily. @@ -31,6 +36,33 @@ internal static class JxlAnsReader [3, 10], [4, 4], [3, 7], [4, 1], [3, 6], [3, 8], [3, 9], [4, 2], ]; + private static readonly sbyte[][] SpecialDistances = + [ + [0, 1], [1, 0], [1, 1], [-1, 1], [0, 2], [2, 0], [1, 2], [-1, 2], + [2, 1], [-2, 1], [2, 2], [-2, 2], [0, 3], [3, 0], [1, 3], [-1, 3], + [3, 1], [-3, 1], [2, 3], [-2, 3], [3, 2], [-3, 2], [0, 4], [4, 0], + [1, 4], [-1, 4], [4, 1], [-4, 1], [3, 3], [-3, 3], [2, 4], [-2, 4], + [4, 2], [-4, 2], [0, 5], [3, 4], [-3, 4], [4, 3], [-4, 3], [5, 0], + [1, 5], [-1, 5], [5, 1], [-5, 1], [2, 5], [-2, 5], [5, 2], [-5, 2], + [4, 4], [-4, 4], [3, 5], [-3, 5], [5, 3], [-5, 3], [0, 6], [6, 0], + [1, 6], [-1, 6], [6, 1], [-6, 1], [2, 6], [-2, 6], [6, 2], [-6, 2], + [4, 5], [-4, 5], [5, 4], [-5, 4], [3, 6], [-3, 6], [6, 3], [-6, 3], + [0, 7], [7, 0], [1, 7], [-1, 7], [5, 5], [-5, 5], [7, 1], [-7, 1], + [4, 6], [-4, 6], [6, 4], [-6, 4], [2, 7], [-2, 7], [7, 2], [-7, 2], + [3, 7], [-3, 7], [7, 3], [-7, 3], [5, 6], [-5, 6], [6, 5], [-6, 5], + [8, 0], [4, 7], [-4, 7], [7, 4], [-7, 4], [8, 1], [8, 2], [6, 6], + [-6, 6], [8, 3], [5, 7], [-5, 7], [7, 5], [-7, 5], [8, 4], [6, 7], + [-6, 7], [7, 6], [-7, 6], [8, 5], [7, 7], [-7, 7], [8, 6], [8, 7] + ]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int SpecialDistance(int index, int multiplier) + { + Span indexDistance = SpecialDistances[index]; + int dist = indexDistance[0] + (multiplier * indexDistance[1]); + return dist > 1 ? dist : 1; + } + public static uint DecodeVariableLengthUint8(JxlBitReader reader) { if (reader.ReadBoolean()) diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsSymbolReader.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsSymbolReader.cs new file mode 100644 index 000000000..8c4a83ca2 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsSymbolReader.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; + +internal sealed class JxlAnsSymbolReader +{ + private const int MaxCheckpointInterval = 512; + + // Use class because the Lz77Window property uses 2KB memory + private sealed class Checkpoint + { + public uint State { get; set; } + + public uint NumToCopy { get; set; } + + public uint CopyPos { get; set; } + + public uint NumDecoded { get; set; } + + public uint[] Lz77Window { get; set; } = new uint[MaxCheckpointInterval]; + } + + private readonly JxlAnsEntry[] aliasTables = []; + private JxlHuffmanDecodingData huffmanData; + private bool usePrefixCode; + private uint state = AnsSignature << 16u; +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlHuffmanDecoder.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlHuffmanDecoder.cs new file mode 100644 index 000000000..991d9c615 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlHuffmanDecoder.cs @@ -0,0 +1,331 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Formats.Jxl.IO; +using SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; + +/// +/// Decodes Huffman codes. +/// +internal sealed class JxlHuffmanDecoder +{ + /// + /// Number of bits that a huffman table uses. + /// + private const int HuffmanTableBits = 8; + + private const int GoalSize = 1 << HuffmanTableBits; + + public const int CodeLengthCodes = 18; + + public const int DefaultCodeLength = 8; + + public const int CodeLengthRepeatCode = 16; + + /// + /// Static Huffman codes for code length code lengths. + /// + private static readonly JxlHuffmanCode[] CodeLengthCodeLengthsCodes = + [ + new(2, 0), new(2, 4), new(2, 3), new(3, 2), new(2, 0), new(2, 4), new(2, 3), new(4, 1), + new(2, 0), new(2, 4), new(2, 3), new(3, 2), new(2, 0), new(2, 4), new(2, 3), new(4, 5), + ]; + + private static ReadOnlySpan CodeLengthCodeOrder => + [ + 1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15, + ]; + + /// + /// Gets or sets the list of huffman codes. + /// + public JxlHuffmanCode[] Table { get; set; } = []; + + public static bool ReadHuffmanCodeLengths(Span codeLengthCodeLengths, int numSymbols, Span codeLengths, JxlBitReader br) + { + int symbol = 0; + int prevCodeLen = DefaultCodeLength; + int repeat = 0; + int repeatCodeLen = 0; + int space = 32768; + + Span table = stackalloc JxlHuffmanCode[32]; + Span counts = stackalloc ushort[16]; + table.Clear(); + counts.Clear(); + + for (int i = 0; i < CodeLengthCodes; i++) + { + counts[codeLengthCodeLengths[i]]++; + } + + if (JxlHuffman.BuildHuffmanTable(table, 5, codeLengthCodeLengths, counts) == 0) + { + return false; + } + + while (symbol < numSymbols && space > 0) + { + JxlHuffmanCode code = table[(int)br.PeekBits32(5u)]; + br.SkipBits32(code.Bits); + byte codeLength = (byte)code.Value; // It is indeed converted from ushort to byte + + if (codeLength < CodeLengthRepeatCode) + { + repeat = 0; + codeLengths[symbol++] = codeLength; + if (codeLength != 0) + { + prevCodeLen = codeLength; + space -= 32768 >> codeLength; + } + } + else + { + int extraBits = codeLength - 14; + byte newLength = 0; + if (codeLength == CodeLengthRepeatCode) + { + newLength = (byte)prevCodeLen; + } + + if (repeatCodeLen != newLength) + { + repeat = 0; + repeatCodeLen = newLength; + } + + int oldRepeat = repeat; + + if (repeat > 0) + { + repeat -= 2; + repeat <<= extraBits; + } + + repeat += (int)br.ReadBits32((uint)extraBits) + 3; + int repeatDelta = repeat - oldRepeat; + + if (symbol + repeatDelta > numSymbols) + { + return false; + } + + codeLengths.Slice(symbol, repeatDelta).Fill((byte)repeatCodeLen); + symbol += repeatDelta; + if (repeatCodeLen != 0) + { + space -= repeatDelta << (15 - repeatCodeLen); + } + } + } + + if (space != 0) + { + return false; + } + + codeLengths[symbol..].Clear(); + return true; + } + + /// + /// Reads a simple Huffman code. + /// + /// Alphabet size (256 at most) + /// Bit-stream reader + /// Output table (must have at most 8 items) + /// Status of the operation + public static bool ReadSimpleCode(int alphabetSize, JxlBitReader br, Span table) + { + int maxBits = (alphabetSize > 1) ? FloorLog2Nonzero(alphabetSize - 1) + 1 : 0; + uint symbolCount = br.ReadBits32(2u) + 1u; + + Span symbols = stackalloc ushort[4]; + symbols.Clear(); // Clearing is necessary. Not every value will be initialized. + + for (int i = 0; i < symbolCount; i++) + { + uint symbol = br.ReadBits32((uint)maxBits); + if (symbol >= alphabetSize) + { + return false; + } + + symbols[i] = (ushort)symbol; + } + + for (int i = 0; i < symbolCount - 1; i++) + { + for (int j = i + 1; j < symbolCount; j++) + { + if (symbols[i] == symbols[j]) + { + return false; + } + } + } + + if (symbolCount == 4) + { + symbolCount += br.ReadBits32(1u); + } + + int tableSize = 1; + switch (symbolCount) + { + case 1: + table[0] = new(0, symbols[0]); + break; + + case 2: + if (symbols[0] > symbols[1]) + { + SwapSymbols(0, 1, symbols); + } + + table[0] = new(1, symbols[0]); + table[1] = new(1, symbols[1]); + tableSize = 2; + break; + + case 3: + if (symbols[1] > symbols[2]) + { + SwapSymbols(1, 2, symbols); + } + + table[0] = new(1, symbols[0]); + table[2] = new(1, symbols[0]); + table[1] = new(2, symbols[1]); + table[3] = new(2, symbols[2]); + tableSize = 4; + break; + + case 4: + for (int i = 0; i < 3; i++) + { + for (int j = i + 1; j < 4; j++) + { + if (symbols[i] > symbols[j]) + { + SwapSymbols(i, j, symbols); + } + } + } + + table[0] = new(2, symbols[0]); + table[2] = new(2, symbols[1]); + table[1] = new(2, symbols[2]); + table[3] = new(2, symbols[3]); + tableSize = 4; + break; + + case 5: + if (symbols[2] > symbols[3]) + { + SwapSymbols(2, 3, symbols); + } + + table[0] = new(1, symbols[0]); + table[1] = new(2, symbols[1]); + table[2] = new(1, symbols[0]); + table[3] = new(3, symbols[2]); + table[4] = new(1, symbols[0]); + table[5] = new(2, symbols[1]); + table[6] = new(1, symbols[0]); + table[7] = new(3, symbols[3]); + tableSize = 8; + break; + + default: + // This should be unreachable. + return false; + } + + while (tableSize != GoalSize) + { + table[tableSize..].CopyTo(table); + tableSize <<= 1; + } + + return true; + } + + public bool ReadFromBitStream(int alphabetSize, JxlBitReader br) + { + if (alphabetSize > (1 << JxlAnsConstants.PrefixMaxBits)) + { + return false; + } + + uint simpleCodeOrSkip = br.ReadBits32(2u); + if (simpleCodeOrSkip == 1u) + { + this.Table = new JxlHuffmanCode[GoalSize]; + return ReadSimpleCode(alphabetSize, br, this.Table); + } + + // The alphabet size is at most 256 + Span codeLengths = stackalloc byte[alphabetSize]; + codeLengths.Clear(); // Zero-initialized in reference software + + Span codeLengthCodeLengths = stackalloc byte[CodeLengthCodes]; + codeLengthCodeLengths.Clear(); // Zero-initialized in reference software + + int space = 32; + int numCodes = 0; + + for (uint i = simpleCodeOrSkip; i < CodeLengthCodes && space > 0; i++) + { + int codeLengthIndex = CodeLengthCodeOrder[(int)i]; + JxlHuffmanCode huff = CodeLengthCodeLengthsCodes[(int)br.PeekBits32(4u)]; + br.SkipBits32(huff.Bits); + byte value = (byte)huff.Value; // It's indeed converted from ushort to byte + codeLengthCodeLengths[codeLengthIndex] = value; + + if (value != 0) + { + space -= 32 >> value; + numCodes++; + } + } + + bool ok = (numCodes == 1 || space == 0) && ReadHuffmanCodeLengths(codeLengthCodeLengths, alphabetSize, codeLengths, br); + + if (!ok) + { + return false; + } + + Span counts = stackalloc ushort[16]; + counts.Clear(); // Zero-initialized + + this.Table = new JxlHuffmanCode[alphabetSize + 376]; + uint tableSize = JxlHuffman.BuildHuffmanTable(this.Table, HuffmanTableBits, codeLengths, counts); + + this.Table = this.Table[..(int)tableSize]; + + return tableSize > 0; + } + + public ushort ReadSymbol(JxlBitReader br) + { + Span table = this.Table.AsSpan()[(int)br.PeekBits32(HuffmanTableBits)..]; + int bitCount = table[0].Bits; + if (bitCount > HuffmanTableBits) + { + br.SkipBits32(HuffmanTableBits); + bitCount -= HuffmanTableBits; + table = table[(int)(table[0].Value + br.PeekBits32((uint)bitCount))..]; + } + + br.SkipBits32(table[0].Bits); + return table[0].Value; + } + + private static void SwapSymbols(int i, int j, Span symbols) => RuntimeUtility.Swap(ref symbols[i], ref symbols[j]); +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchBlendMode.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchBlendMode.cs new file mode 100644 index 000000000..755cfe4eb --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchBlendMode.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; + +internal enum JxlPatchBlendMode : byte +{ + None, + Replace, + Add, + Multiply, + BlendAbove, + BlendBelow, + AlphaWeightedAddAbove, + AlphaWeightedAddBelow +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchBlending.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchBlending.cs new file mode 100644 index 000000000..7cedfc305 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchBlending.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; + +internal struct JxlPatchBlending +{ + public JxlPatchBlendMode Mode; + public int AlphaChannel; + public bool Clamp; +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchDictionary.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchDictionary.cs new file mode 100644 index 000000000..1b7232566 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchDictionary.cs @@ -0,0 +1,256 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; + +internal sealed class JxlPatchDictionary +{ + private struct PatchTreeNode + { + public long LeftChild; + public long RightChild; + public int YCenter; + public int Start; + public int Count; + } + + private struct SortedPatch + { + public int First; + public int Second; + } + + private readonly JxlReferenceFrame[] referenceFrames = new JxlReferenceFrame[4]; + private readonly List positions = []; + private readonly List referencePositions = []; + private readonly List blendings = []; + private int blendingsStride; + private readonly List patchTree = []; + private readonly List numPatches = []; + private readonly List sortedPatchesY0 = []; + private readonly List sortedPatchesY1 = []; + + public bool HasAny => this.positions.Count > 0; + + public void Clear() + { + this.positions.Clear(); + ComputePatchTree(); + } + + public void Decode( + JxlMemoryManager memoryManager, + JxlBitReader br, + ulong xsize, + ulong ysize, + ulong numExtraChannels, + ref bool usesExtraChannels) + { + this.positions.Clear(); + this.blendingsStride = (int)(numExtraChannels + 1); + + List contextMap = []; + var code = new JxlAnsCode(); + + var status = DecodeHistograms( + memoryManager, + br, + PatchDictionaryContexts, + code, + contextMap); + + JxlAnsSymbolReader decoder = JxlAnsSymbolReader.Create(code, br); + + ulong ReadNum(int context) + => decoder.ReadHybridUint(context, br, contextMap); + + ulong numRefPatch = ReadNum(kNumRefPatchContext); + + ulong numPixels = xsize * ysize; + ulong maxRefPatches = 1024 + (numPixels / 4); + ulong maxPatches = maxRefPatches * 4; + ulong maxBlendingInfos = maxPatches * 4; + + if (numRefPatch > maxRefPatches) + { + throw new InvalidOperationException("Too many patches in dictionary"); + } + + ulong totalPatches = 0; + ulong nextSize = 1; + + for (ulong id = 0; id < numRefPatch; id++) + { + JxlPatchReferencePosition refPos = new() + { + Ref = ReadNum(kReferenceFrameContext) + }; + + if (refPos.Ref >= kMaxNumReferenceFrames || this.referenceFrames[(int)refPos.Ref].Frame.XSize == 0) + { + throw new InvalidOperationException("Invalid reference frame ID"); + } + + if (!this.referenceFrames[refPos.Ref].IsInXYB) + { + throw new InvalidOperationException("Patches cannot use frames saved post color transforms"); + } + + JxlImageBundle ib = this.referenceFrames[refPos.Ref].Frame; + + refPos.X0 = ReadNum(kPatchReferencePositionContext); + refPos.Y0 = ReadNum(kPatchReferencePositionContext); + refPos.XSize = ReadNum(kPatchSizeContext) + 1; + refPos.YSize = ReadNum(kPatchSizeContext) + 1; + + if (refPos.X0 + refPos.XSize > ib.XSize) + { + throw new InvalidOperationException("Invalid position specified in reference frame"); + } + + if (refPos.Y0 + refPos.YSize > ib.YSize) + { + throw new InvalidOperationException("Invalid position specified in reference frame"); + } + + ulong idCount = ReadNum(kPatchCountContext); + + if (idCount > maxPatches) + { + throw new InvalidOperationException("Too many patches in dictionary"); + } + + idCount++; + + totalPatches += idCount; + + if (totalPatches > maxPatches) + { + throw new InvalidOperationException("Too many patches in dictionary"); + } + + if (nextSize < totalPatches) + { + nextSize *= 2; + nextSize = Math.Min(nextSize, maxPatches); + } + + if (nextSize * (ulong)this.blendingsStride > maxBlendingInfos) + { + throw new InvalidOperationException("Too many patches in dictionary"); + } + + _ = this.blendings.EnsureCapacity((int)nextSize); + _ = this.blendings.EnsureCapacity((int)(nextSize * (ulong)this.blendingsStride)); + + bool chooseAlpha = numExtraChannels > 1; + + for (ulong i = 0; i < idCount; i++) + { + JxlPatchPosition pos = new() + { + ReferencePositionIndex = this.referencePositions.Count + }; + + if (i == 0) + { + pos.X = ReadNum(kPatchPositionContext); + pos.Y = ReadNum(kPatchPositionContext); + } + else + { + long deltaX = JxlPackSigned.UnpackSigned(ReadNum(kPatchOffsetContext)); + + if (deltaX < 0 && (int)(-deltaX) > this.positions[^1].X) + { + throw new InvalidOperationException($"Invalid patch: negative x coordinate ({this.positions[^1].X}, delta {deltaX})"); + } + + pos.X = (int)(this.positions[^1].X + deltaX); + + long deltaY = JxlPackSigned.UnpackSigned(ReadNum(kPatchOffsetContext)); + + if (deltaY < 0 && (int)(-deltaY) > this.positions[^1].Y) + { + throw new InvalidOperationException($"Invalid patch: negative y coordinate ({this.positions[^1].Y}, delta {deltaY})"); + } + + pos.Y = (int)(this.positions[^1].Y + deltaY); + } + + if (pos.X + refPos.XSize > (int)xsize) + { + throw new InvalidOperationException($"Invalid patch x: {pos.X} + {refPos.XSize} > {xsize}"); + } + + if (pos.Y + refPos.YSize > (int)ysize) + { + throw new InvalidOperationException($"Invalid patch y: {pos.Y} + {refPos.YSize} > {ysize}"); + } + + for (int j = 0; j < this.blendingsStride; j++) + { + uint blendMode = (uint)ReadNum(kPatchBlendModeContext); + + if (blendMode >= kNumPatchBlendModes) + { + throw new InvalidOperationException($"Invalid patch blend mode: {blendMode}"); + } + + JxlPatchBlending info = new() + { + Mode = (JxlPatchBlendMode)blendMode + }; + + if (UsesAlpha(info.Mode)) + { + usesExtraChannels = true; + } + + if (info.Mode != JxlPatchBlendMode.None && j > 0) + { + usesExtraChannels = true; + } + + if (UsesAlpha(info.Mode) && chooseAlpha) + { + info.AlphaChannel = (uint)ReadNum(kPatchAlphaChannelContext); + + if (info.AlphaChannel >= (int)numExtraChannels) + { + throw new InvalidOperationException($"Invalid alpha channel for blending: {info.AlphaChannel} out of {numExtraChannels}"); + } + } + else + { + info.AlphaChannel = 0; + } + + if (UsesClamp(info.Mode)) + { + info.Clamp = ReadNum(kPatchClampContext) != 0; + } + else + { + info.Clamp = false; + } + + this.blendings.Add(info); + } + + this.positions.Add(pos); + } + + this.positions.Add(refPos); + } + + this.positions.TrimExcess(); + + if (!decoder.CheckAnsFinalState()) + { + throw new InvalidOperationException("ANS checksum failure."); + } + + this.ComputePatchTree(); + } +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchPosition.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchPosition.cs new file mode 100644 index 000000000..264d694e8 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchPosition.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; + +internal struct JxlPatchPosition +{ + public int X; + public int Y; + public int ReferencePositionIndex; +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchReferencePosition.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchReferencePosition.cs new file mode 100644 index 000000000..8e11eb55a --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchReferencePosition.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; + +internal struct JxlPatchReferencePosition +{ + public int Ref; + public int X0; + public int Y0; + public int XSize; + public int YSize; +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlColorCorrelationMap.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlColorCorrelationMap.cs index 89ae3f729..b2e2581f7 100644 --- a/src/ImageSharp/Formats/Jxl/Processing/JxlColorCorrelationMap.cs +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlColorCorrelationMap.cs @@ -19,7 +19,7 @@ internal sealed class JxlColorCorrelationMap public bool DecodeDc(JxlBitReader reader) => this.Base.DecodeDc(reader); - public static JxlColorCorrelationMap Create(Configuration configuration, int width, int height, bool xyb) + public static JxlColorCorrelationMap Create(Configuration configuration, int width, int height, bool xyb = true) { JxlColorCorrelationMap map = new(); diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlDctQuantWeightParameters.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlDctQuantWeightParameters.cs new file mode 100644 index 000000000..288d12995 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlDctQuantWeightParameters.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing; + +internal sealed class JxlDctQuantWeightParameters +{ + private const int Log2MaxDistanceBands = 4; + private const int MaxDistanceBands = 1 + (1 << Log2MaxDistanceBands); + + private int numDistanceBands; + private readonly float[][] distanceBands; + + public JxlDctQuantWeightParameters() + { + this.distanceBands = new float[3][]; + for (int i = 0; i < 3; i++) + { + this.distanceBands[i] = new float[MaxDistanceBands]; + } + } + + public JxlDctQuantWeightParameters(float[][] distanceBands, int numDistanceBands) + { + this.numDistanceBands = numDistanceBands; + this.distanceBands = distanceBands; + } +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlImageFeatures.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlImageFeatures.cs new file mode 100644 index 000000000..142edd071 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlImageFeatures.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing; + +/// +/// Image features for the JPEG XL passes decoder +/// +internal sealed class JxlImageFeatures +{ + /// + /// Gets or sets noise parameters for the passes decoder + /// + public JxlNoiseParameters NoiseParameters { get; set; } = new(); + + /// + /// Gets or sets patch dictionary for the passes decoder + /// + public JxlPatchDictionary PatchDictionary { get; set; } = new(); + + /// + /// Gets or sets splines for the passes decoder + /// + public JxlSplines Splines { get; set; } = new(); +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlImageOperations.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlImageOperations.cs new file mode 100644 index 000000000..f1b50596f --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlImageOperations.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Jxl.Memory; + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing; + +internal static class JxlImageOperations +{ + /// + /// Returns true if first image has same width and height as the second image. + /// + /// First image + /// Second image + /// True if width and height is equal. + public static bool SameSize(JxlPlaneBase a, JxlPlaneBase b) => a.XSize == b.XSize && a.YSize == b.YSize; + + public static bool CopyImage(JxlPlane from, JxlPlane to) + where T : unmanaged + { + if (!SameSize(from, to)) + { + return false; + } + + if (from.XSize == 0 || from.YSize == 0) + { + return true; + } + + for (int y = 0; y < from.YSize; y++) + { + Span rowFrom = from.GetRow(y); + Span rowTo = to.GetRow(y); + rowFrom.CopyTo(rowTo); + } + + return true; + } + + public static bool CopyImageTo(Rectangle rectFrom, JxlPlane from, Rectangle rectTo, JxlPlane to) + where T : unmanaged + { + if (rectFrom != rectTo) + { + return false; + } + + + } +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs index 6638f8600..8016ec51c 100644 --- a/src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs @@ -1,10 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; - namespace SixLabors.ImageSharp.Formats.Jxl.Processing; /// @@ -12,79 +8,35 @@ namespace SixLabors.ImageSharp.Formats.Jxl.Processing; /// internal static class JxlInverseMtf { - // NOTE: here we use Vector512 to store 64 bytes in a - // more efficient manner. However, it doesn't necessarily - // require 512-bit CPU vector support. - // If the user's CPU has 256-bit vectors, the JIT will emit - // such instructions for each half. Likewise, if the user's - // CPU only goes up to 128-bit vectors, the JIT will emit - // 128-bit vector code for each quarter. And if the CPU - // doesn't support SIMD at all, the JIT will emit scalar - // instructions. - public static void MoveToFront(Span v, byte index) + public static void MoveToFront(Span values, byte index) { - byte value = v[index]; - byte i = index; - - ref byte vR = ref MemoryMarshal.GetReference(v); - - if (i < 4) - { - for (; i != 0; --i) - { - v[i] = v[i - 1]; - } - } - else - { - int tail = i & 63; - - if (tail != 0) - { - i -= (byte)tail; - Vector512 vec = Vector512.LoadUnsafe(ref Unsafe.Add(ref vR, i)); - Vector512 prev = Vector512.LoadUnsafe(ref Unsafe.Add(ref vR, i + 1)); + byte value = values[index]; - // TODO: optimize this? - Span maskBytes = stackalloc byte[64]; - - for (int j = 0; j < 64; j++) - { - maskBytes[j] = (byte)(j < tail ? 0xFF : 0); - } - - Vector512 mask = Vector512.Create(maskBytes); - Vector512 filter = Vector512.ConditionalSelect(mask, vec, prev); - filter.StoreUnsafe(ref Unsafe.Add(ref vR, i + 1)); - } - - while (i != 0) - { - i -= 64; - Vector512 vec = Vector512.LoadUnsafe(ref Unsafe.Add(ref vR, i)); - vec.StoreUnsafe(ref Unsafe.Add(ref vR, i + 1)); - } - } - - v[0] = value; + // CopyTo supports overlapping source and destination regions. + values[..index].CopyTo(values[1..]); + values[0] = value; } - public static void InverseMoveToFrontTransform(Span v, int vLength) + public static void InverseMoveToFrontTransform(Span values) { - Span mtf = stackalloc byte[256 + 64]; - for (int i = 0; i < 256; i++) + Span table = stackalloc byte[256]; + + for (int i = 0; i < table.Length; i++) { - mtf[i] = (byte)i; + table[i] = (byte)i; } - for (int i = 0; i < vLength; i++) + for (int i = 0; i < values.Length; i++) { - byte index = v[i]; - v[i] = mtf[index]; + byte index = values[i]; + byte value = table[index]; + values[i] = value; if (index != 0) { - MoveToFront(mtf, index); + // CopyTo handles the overlap and shifts the preceding entries. + table[..index].CopyTo(table[1..]); + table[0] = value; } } } diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlPassesSharedState.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlPassesSharedState.cs new file mode 100644 index 000000000..806c302f5 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlPassesSharedState.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; +using SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; +using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing; + +internal class JxlPassesSharedState +{ + public JxlCodecMetadata CodecMetadata { get; set; } = new(); + + public JxlFrameDimensions FrameDimensions { get; set; } + + public JxlAcStrategyImage AcStrategy { get; set; } + + public JxlDequantMatrices Matrices { get; set; } = new(); + + public JxlQuantizer Quantizer { get; set; } + + public JxlImageI RawQuantField { get; set; } + + public JxlImageB EpfSharpness { get; set; } + + public JxlColorCorrelationMap ColorMap { get; set; } + + public JxlImageFeatures ImageFeatures { get; set; } = new(); + + public int CoeffOrderSize { get; set; } + + public List CoeffOrders { get; set; } = []; + + public JxlImageB QuantDc { get; set; } + + public JxlImage3F DcStorage { get; set; } + + public JxlImage3F Dc { get; set; } + + public JxlBlockContextMap BlockContextMap { get; set; } = new(); + + public JxlImage3F[] DcFrames { get; set; } = new JxlImage3F[4]; + + public JxlReferenceFrame[] ReferenceFrames { get; set; } = new JxlReferenceFrame[4]; + + public int NumHistograms { get; set; } + + public JxlPassesSharedState(Configuration configuration, JxlFrameHeader frameHeader, bool encoder) + { + if (frameHeader.Metadata is null) + { + throw new InvalidOperationException("The frame header metadata is missing"); + } + + this.CodecMetadata = frameHeader.Metadata; + this.FrameDimensions = frameHeader.FrameDimensions; + this.ImageFeatures.PatchDictionary.SetShared(this.ImageFeatures.ReferenceFrames); + + JxlFrameDimensions dimensions = frameHeader.FrameDimensions; + + this.AcStrategy = JxlAcStrategyImage.Create(configuration, dimensions.XSizeBlocks, dimensions.YSizeBlocks); + this.RawQuantField = new JxlImageI(configuration, dimensions.XSizeBlocks, dimensions.YSizeBlocks); + this.EpfSharpness = new JxlImageB(configuration, dimensions.XSizeBlocks, dimensions.YSizeBlocks); + this.ColorMap = JxlColorCorrelationMap.Create(configuration, dimensions.XSize, dimensions.YSize); + + this.CoeffOrderSize = JxlCoefficientOrder.CoefficientOrderMaxSize; + + if (encoder && + this.CoeffOrders.Count < (frameHeader.Passes.NumPasses & JxlCoefficientOrder.CoefficientOrderMaxSize) && + frameHeader.Encoding == JxlFrameEncoding.VarDct) + { + // we add the padding to CoeffOrders so its length is equal to the variable upperBound + int upperBound = frameHeader.Passes.NumPasses & JxlCoefficientOrder.CoefficientOrderMaxSize; + int length = this.CoeffOrders.Count; + int delta = upperBound - length; + + for (int i = 0; i < delta; i++) + { + this.CoeffOrders.Add(0); // default constant + } + } + + this.QuantDc = new JxlImageB(configuration, dimensions.XSizeBlocks, dimensions.YSizeBlocks); + + bool useDcFrame = (frameHeader.Flags & (ulong)JxlFrameHeaderFlags.Dc) != 0; + if (!encoder && useDcFrame) + { + if (frameHeader.DcLevel == 4) + { + throw new InvalidOperationException("DC level for DC frames cannot be equal to 4"); + } + + this.DcStorage = new JxlImage3F(); + this.Dc = this.DcFrames[(int)frameHeader.DcLevel]; + + if (this.Dc.XSize == 0) + { + throw new InvalidOperationException("DC frame was specified for DC Level = " + frameHeader.DcLevel + ", but frame wasn't decoded with level " + frameHeader.DcLevel + 1); + } + + this.QuantDc.Clear(); + } + else + { + this.DcStorage = new JxlImage3F(configuration, dimensions.XSizeBlocks, dimensions.YSizeBlocks); + this.Dc = this.DcStorage; + } + + this.Quantizer = new(this.Matrices); + } +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlPatchContext.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlPatchContext.cs new file mode 100644 index 000000000..a79a4720e --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlPatchContext.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing; + +/// +/// Context numbers for patch decoding +/// +internal enum JxlPatchContext : byte +{ + NumRefPatch = 0, + + ReferenceFrame = 1, + + PatchSize = 2, + + PatchReferencePosition = 3, + + PatchPosition = 4, + + PatchBlendMode = 5, + + PatchOffset = 6, + + PatchCount = 7, + + PatchAlphaChannel = 8, + + PatchClamp = 9, + + NumPatchDictionaryContexts +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlQuantWeights.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlQuantWeights.cs new file mode 100644 index 000000000..2c50d92af --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlQuantWeights.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing; + +internal static class JxlQuantWeights +{ + public const int MaxQuantTableSize = JxlAcStrategy.MaximumCoefficientArea; + + public const int NumPredefinedTables = 1; + + public const int CeilLog2NumPredefinedTables = 0; + + public const int Log2NumQuantModes = 3; +}