From f19c8797ec804b48602b6188e29a7478b61a1f37 Mon Sep 17 00:00:00 2001
From: winscripter <142818255+winscripter@users.noreply.github.com>
Date: Sat, 8 Aug 2026 18:17:35 +0400
Subject: [PATCH] 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)
---
.../Jxl/IO/Entropy/JxlAnsLz77Parameters.cs | 70 +++-
.../Formats/Jxl/IO/JxlHuffmanCode.cs | 6 +-
.../Jxl/Memory/ImageTypes/JxlImage3B.cs | 5 +
.../Jxl/Memory/ImageTypes/JxlImage3F.cs | 5 +
.../Jxl/Memory/ImageTypes/JxlImage3I.cs | 5 +
.../Jxl/Memory/ImageTypes/JxlImage3S.cs | 5 +
.../Jxl/Memory/ImageTypes/JxlImage3U.cs | 5 +
.../Formats/Jxl/Memory/JxlImage3{T}.cs | 11 +-
.../Jxl/Processing/Decoder/JxlAnsCode.cs | 50 +++
.../Jxl/Processing/Decoder/JxlAnsReader.cs | 32 ++
.../Processing/Decoder/JxlAnsSymbolReader.cs | 30 ++
.../Processing/Decoder/JxlHuffmanDecoder.cs | 331 ++++++++++++++++++
.../Processing/Decoder/JxlPatchBlendMode.cs | 16 +
.../Processing/Decoder/JxlPatchBlending.cs | 11 +
.../Processing/Decoder/JxlPatchDictionary.cs | 256 ++++++++++++++
.../Processing/Decoder/JxlPatchPosition.cs | 11 +
.../Decoder/JxlPatchReferencePosition.cs | 13 +
.../Jxl/Processing/JxlColorCorrelationMap.cs | 2 +-
.../Processing/JxlDctQuantWeightParameters.cs | 28 ++
.../Jxl/Processing/JxlImageFeatures.cs | 27 ++
.../Jxl/Processing/JxlImageOperations.cs | 51 +++
.../Formats/Jxl/Processing/JxlInverseMtf.cs | 82 +----
.../Jxl/Processing/JxlPassesSharedState.cs | 111 ++++++
.../Formats/Jxl/Processing/JxlPatchContext.cs | 32 ++
.../Formats/Jxl/Processing/JxlQuantWeights.cs | 15 +
25 files changed, 1132 insertions(+), 78 deletions(-)
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsCode.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsSymbolReader.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlHuffmanDecoder.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchBlendMode.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchBlending.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchDictionary.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchPosition.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlPatchReferencePosition.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlDctQuantWeightParameters.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlImageFeatures.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlImageOperations.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlPassesSharedState.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlPatchContext.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlQuantWeights.cs
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;
+}