Browse Source

Add more implementations and prototypes

- Add decoding of Huffman Codes (see dec_huffman.cc and dec_huffman.h)
- Add constructors to JxlImage3* classes
- Make JxlColorCorrelationMap.Create 'xyb' parameter use true as a default value
- Prototype of DCT quant weight parameters
- Add passes shared state (see passes_state.cc and passes_state.h)
- Add prototype for image operations (see image_ops.cc and image_ops.h)
- Simplify inverse MTF (Move to Front) transform
- Add patch context (see patch_dictionary_internal.h)
- Add prototype of quantizer weights
- Add 2nd prototype of ANS entropy decoding (see dec_ans.cc and dec_ans.h)
- Add prototype of patch dictionary decoding (see dec_patch_dictionary.cc and dec_patch_dictionary.h)
pull/3153/head
winscripter 4 weeks ago
parent
commit
f19c8797ec
  1. 70
      src/ImageSharp/Formats/Jxl/IO/Entropy/JxlAnsLz77Parameters.cs
  2. 6
      src/ImageSharp/Formats/Jxl/IO/JxlHuffmanCode.cs
  3. 5
      src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3B.cs
  4. 5
      src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3F.cs
  5. 5
      src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3I.cs
  6. 5
      src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3S.cs
  7. 5
      src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3U.cs
  8. 11
      src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs
  9. 50
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsCode.cs
  10. 32
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsReader.cs
  11. 30
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsSymbolReader.cs
  12. 331
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlHuffmanDecoder.cs
  13. 16
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchBlendMode.cs
  14. 11
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchBlending.cs
  15. 256
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchDictionary.cs
  16. 11
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchPosition.cs
  17. 13
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchReferencePosition.cs
  18. 2
      src/ImageSharp/Formats/Jxl/Processing/JxlColorCorrelationMap.cs
  19. 28
      src/ImageSharp/Formats/Jxl/Processing/JxlDctQuantWeightParameters.cs
  20. 27
      src/ImageSharp/Formats/Jxl/Processing/JxlImageFeatures.cs
  21. 51
      src/ImageSharp/Formats/Jxl/Processing/JxlImageOperations.cs
  22. 82
      src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs
  23. 111
      src/ImageSharp/Formats/Jxl/Processing/JxlPassesSharedState.cs
  24. 32
      src/ImageSharp/Formats/Jxl/Processing/JxlPatchContext.cs
  25. 15
      src/ImageSharp/Formats/Jxl/Processing/JxlQuantWeights.cs

70
src/ImageSharp/Formats/Jxl/IO/Entropy/JxlAnsLz77Parameters.cs

@ -7,15 +7,75 @@ namespace SixLabors.ImageSharp.Formats.Jxl.IO.Entropy;
internal sealed class JxlAnsLz77Parameters : IJxlFields 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 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;
}
} }

6
src/ImageSharp/Formats/Jxl/IO/JxlHuffmanCode.cs

@ -6,15 +6,15 @@ namespace SixLabors.ImageSharp.Formats.Jxl.IO;
/// <summary> /// <summary>
/// A single Huffman code. /// A single Huffman code.
/// </summary> /// </summary>
internal struct JxlHuffmanCode internal struct JxlHuffmanCode(byte bits, ushort value)
{ {
/// <summary> /// <summary>
/// Number of bits for this symbol. /// Number of bits for this symbol.
/// </summary> /// </summary>
public byte Bits; public byte Bits = bits;
/// <summary> /// <summary>
/// Symbol value/offset. /// Symbol value/offset.
/// </summary> /// </summary>
public ushort Value; public ushort Value = value;
} }

5
src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3B.cs

@ -11,4 +11,9 @@ internal sealed class JxlImage3B : JxlImage3<byte>
public JxlImage3B() public JxlImage3B()
{ {
} }
public JxlImage3B(Configuration configuration, int xSize, int ySize)
: base(configuration, xSize, ySize)
{
}
} }

5
src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3F.cs

@ -11,4 +11,9 @@ internal sealed class JxlImage3F : JxlImage3<float>
public JxlImage3F() public JxlImage3F()
{ {
} }
public JxlImage3F(Configuration configuration, int xSize, int ySize)
: base(configuration, xSize, ySize)
{
}
} }

5
src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3I.cs

@ -11,4 +11,9 @@ internal sealed class JxlImage3I : JxlImage3<int>
public JxlImage3I() public JxlImage3I()
{ {
} }
public JxlImage3I(Configuration configuration, int xSize, int ySize)
: base(configuration, xSize, ySize)
{
}
} }

5
src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3S.cs

@ -11,4 +11,9 @@ internal sealed class JxlImage3S : JxlImage3<short>
public JxlImage3S() public JxlImage3S()
{ {
} }
public JxlImage3S(Configuration configuration, int xSize, int ySize)
: base(configuration, xSize, ySize)
{
}
} }

5
src/ImageSharp/Formats/Jxl/Memory/ImageTypes/JxlImage3U.cs

@ -11,4 +11,9 @@ internal sealed class JxlImage3U : JxlImage3<ushort>
public JxlImage3U() public JxlImage3U()
{ {
} }
public JxlImage3U(Configuration configuration, int xSize, int ySize)
: base(configuration, xSize, ySize)
{
}
} }

11
src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs

@ -17,6 +17,9 @@ internal class JxlImage3<T> : IDisposable
{ {
} }
public JxlImage3(Configuration configuration, int xSize, int ySize)
=> this.Allocate(configuration, xSize, ySize);
public JxlImage3(JxlImage3<T> other) public JxlImage3(JxlImage3<T> other)
{ {
for (int i = 0; i < PlaneCount; i++) for (int i = 0; i < PlaneCount; i++)
@ -54,15 +57,15 @@ internal class JxlImage3<T> : IDisposable
} }
public static JxlImage3<T> Create(Configuration configuration, int xSize, int ySize) public static JxlImage3<T> Create(Configuration configuration, int xSize, int ySize)
=> new(configuration, xSize, ySize);
public void Allocate(Configuration configuration, int xSize, int ySize)
{ {
JxlPlane<T> plane0 = JxlPlane<T>.Create(configuration, xSize, ySize); JxlPlane<T> plane0 = JxlPlane<T>.Create(configuration, xSize, ySize);
JxlPlane<T> plane1 = JxlPlane<T>.Create(configuration, xSize, ySize); JxlPlane<T> plane1 = JxlPlane<T>.Create(configuration, xSize, ySize);
JxlPlane<T> plane2 = JxlPlane<T>.Create(configuration, xSize, ySize); JxlPlane<T> plane2 = JxlPlane<T>.Create(configuration, xSize, ySize);
return new JxlImage3<T>() this.planes = [plane0, plane1, plane2];
{
planes = [plane0, plane1, plane2]
};
} }
public bool ShrinkTo(int x, int y) public bool ShrinkTo(int x, int y)

50
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<JxlHuffmanDecodingData> HuffmanData { get; set; } = [];
public List<JxlAnsHybridUIntConfiguration> UIntConfig { get; set; } = [];
public List<int> 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<JxlAnsHybridUIntConfiguration> 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);
}
}

32
src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsReader.cs

@ -2,12 +2,17 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; using SixLabors.ImageSharp.Formats.Jxl.IO.Entropy;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder;
internal static class JxlAnsReader internal static class JxlAnsReader
{ {
private const int WindowSize = 1 << 20;
private const int NumSpecialDistances = 120;
// Prefer jagged arrays over multidimensional arrays // Prefer jagged arrays over multidimensional arrays
// for performance. Collection expressions help represent // for performance. Collection expressions help represent
// jagged arrays easily. // 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], [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<sbyte> indexDistance = SpecialDistances[index];
int dist = indexDistance[0] + (multiplier * indexDistance[1]);
return dist > 1 ? dist : 1;
}
public static uint DecodeVariableLengthUint8(JxlBitReader reader) public static uint DecodeVariableLengthUint8(JxlBitReader reader)
{ {
if (reader.ReadBoolean()) if (reader.ReadBoolean())

30
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;
}

331
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;
/// <summary>
/// Decodes Huffman codes.
/// </summary>
internal sealed class JxlHuffmanDecoder
{
/// <summary>
/// Number of bits that a huffman table uses.
/// </summary>
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;
/// <summary>
/// Static Huffman codes for code length code lengths.
/// </summary>
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<byte> CodeLengthCodeOrder =>
[
1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15,
];
/// <summary>
/// Gets or sets the list of huffman codes.
/// </summary>
public JxlHuffmanCode[] Table { get; set; } = [];
public static bool ReadHuffmanCodeLengths(Span<byte> codeLengthCodeLengths, int numSymbols, Span<byte> codeLengths, JxlBitReader br)
{
int symbol = 0;
int prevCodeLen = DefaultCodeLength;
int repeat = 0;
int repeatCodeLen = 0;
int space = 32768;
Span<JxlHuffmanCode> table = stackalloc JxlHuffmanCode[32];
Span<ushort> 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;
}
/// <summary>
/// Reads a simple Huffman code.
/// </summary>
/// <param name="alphabetSize">Alphabet size (256 at most)</param>
/// <param name="br">Bit-stream reader</param>
/// <param name="table">Output table (must have at most 8 items)</param>
/// <returns>Status of the operation</returns>
public static bool ReadSimpleCode(int alphabetSize, JxlBitReader br, Span<JxlHuffmanCode> table)
{
int maxBits = (alphabetSize > 1) ? FloorLog2Nonzero(alphabetSize - 1) + 1 : 0;
uint symbolCount = br.ReadBits32(2u) + 1u;
Span<ushort> 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<byte> codeLengths = stackalloc byte[alphabetSize];
codeLengths.Clear(); // Zero-initialized in reference software
Span<byte> 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<ushort> 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<JxlHuffmanCode> 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<ushort> symbols) => RuntimeUtility.Swap(ref symbols[i], ref symbols[j]);
}

16
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
}

11
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;
}

256
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<JxlPatchPosition> positions = [];
private readonly List<JxlPatchReferencePosition> referencePositions = [];
private readonly List<JxlPatchBlending> blendings = [];
private int blendingsStride;
private readonly List<PatchTreeNode> patchTree = [];
private readonly List<int> numPatches = [];
private readonly List<SortedPatch> sortedPatchesY0 = [];
private readonly List<SortedPatch> 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<byte> 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();
}
}

11
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;
}

13
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;
}

2
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 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(); JxlColorCorrelationMap map = new();

28
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;
}
}

27
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;
/// <summary>
/// Image features for the JPEG XL passes decoder
/// </summary>
internal sealed class JxlImageFeatures
{
/// <summary>
/// Gets or sets noise parameters for the passes decoder
/// </summary>
public JxlNoiseParameters NoiseParameters { get; set; } = new();
/// <summary>
/// Gets or sets patch dictionary for the passes decoder
/// </summary>
public JxlPatchDictionary PatchDictionary { get; set; } = new();
/// <summary>
/// Gets or sets splines for the passes decoder
/// </summary>
public JxlSplines Splines { get; set; } = new();
}

51
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
{
/// <summary>
/// Returns true if first image has same width and height as the second image.
/// </summary>
/// <param name="a">First image</param>
/// <param name="b">Second image</param>
/// <returns>True if width and height is equal.</returns>
public static bool SameSize(JxlPlaneBase a, JxlPlaneBase b) => a.XSize == b.XSize && a.YSize == b.YSize;
public static bool CopyImage<T>(JxlPlane<T> from, JxlPlane<T> 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<T> rowFrom = from.GetRow(y);
Span<T> rowTo = to.GetRow(y);
rowFrom.CopyTo(rowTo);
}
return true;
}
public static bool CopyImageTo<T>(Rectangle rectFrom, JxlPlane<T> from, Rectangle rectTo, JxlPlane<T> to)
where T : unmanaged
{
if (rectFrom != rectTo)
{
return false;
}
}
}

82
src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs

@ -1,10 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // 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; namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
/// <summary> /// <summary>
@ -12,79 +8,35 @@ namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
/// </summary> /// </summary>
internal static class JxlInverseMtf internal static class JxlInverseMtf
{ {
// NOTE: here we use Vector512 to store 64 bytes in a public static void MoveToFront(Span<byte> values, byte index)
// 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<byte> v, byte index)
{ {
byte value = v[index]; byte value = values[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<byte> vec = Vector512.LoadUnsafe(ref Unsafe.Add(ref vR, i));
Vector512<byte> prev = Vector512.LoadUnsafe(ref Unsafe.Add(ref vR, i + 1));
// TODO: optimize this? // CopyTo supports overlapping source and destination regions.
Span<byte> maskBytes = stackalloc byte[64]; values[..index].CopyTo(values[1..]);
values[0] = value;
for (int j = 0; j < 64; j++)
{
maskBytes[j] = (byte)(j < tail ? 0xFF : 0);
}
Vector512<byte> mask = Vector512.Create<byte>(maskBytes);
Vector512<byte> filter = Vector512.ConditionalSelect(mask, vec, prev);
filter.StoreUnsafe(ref Unsafe.Add(ref vR, i + 1));
}
while (i != 0)
{
i -= 64;
Vector512<byte> vec = Vector512.LoadUnsafe(ref Unsafe.Add(ref vR, i));
vec.StoreUnsafe(ref Unsafe.Add(ref vR, i + 1));
}
}
v[0] = value;
} }
public static void InverseMoveToFrontTransform(Span<byte> v, int vLength) public static void InverseMoveToFrontTransform(Span<byte> values)
{ {
Span<byte> mtf = stackalloc byte[256 + 64]; Span<byte> table = stackalloc byte[256];
for (int i = 0; i < 256; i++)
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]; byte index = values[i];
v[i] = mtf[index]; byte value = table[index];
values[i] = value;
if (index != 0) if (index != 0)
{ {
MoveToFront(mtf, index); // CopyTo handles the overlap and shifts the preceding entries.
table[..index].CopyTo(table[1..]);
table[0] = value;
} }
} }
} }

111
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<byte> 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);
}
}

32
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;
/// <summary>
/// Context numbers for patch decoding
/// </summary>
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
}

15
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;
}
Loading…
Cancel
Save