From 27a848cd256ed439752b92d1ff3ed7280e034d34 Mon Sep 17 00:00:00 2001
From: winscripter <142818255+winscripter@users.noreply.github.com>
Date: Wed, 12 Aug 2026 16:06:41 +0400
Subject: [PATCH] Add DCT, transpose, and performance improvements
- Add a slice & assert to JxlHuffmanDecoder alphabetSize to allocate at most 256 items
- Use [0, 0] instead of stackalloc[2] followed by Clear() in JxlAnsReader
- Add assert to Butteraugli ComputeKernel method & use float for Butteraugli Wmul & use InlineArray
- Add transpose.
- Note: transpose is scalar, it doesn't support SIMD yet
- Add shared constants & file signature
- Improve while loop in JxlImageOperations.Mirror
- Floating-point Discrete Cosine Transform (1D and 2D)
- Add an inline array of 2 items
Source files implemented from libjxl with this commit:
- dct-inl.h
- dct_block-inl.h
- transpose-inl.h
---
src/ImageSharp/Formats/Jxl/InlineArrays.cs | 9 +
.../Jxl/Processing/Butteraugli/Butteraugli.cs | 20 +-
.../Jxl/Processing/Decoder/JxlAnsReader.cs | 3 +-
.../Processing/Decoder/JxlHuffmanDecoder.cs | 5 +-
.../Formats/Jxl/Processing/JxlDct.cs | 305 ++++++++++++++++++
.../Formats/Jxl/Processing/JxlDctOutput.cs | 52 +++
.../Formats/Jxl/Processing/JxlDctScales.cs | 27 ++
.../Formats/Jxl/Processing/JxlDctSource.cs | 55 ++++
.../Jxl/Processing/JxlImageOperations.cs | 8 +-
.../Formats/Jxl/Processing/JxlShared.cs | 30 ++
.../Formats/Jxl/Processing/JxlTranspose.cs | 22 ++
11 files changed, 522 insertions(+), 14 deletions(-)
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlDct.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlDctOutput.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlDctSource.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlShared.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlTranspose.cs
diff --git a/src/ImageSharp/Formats/Jxl/InlineArrays.cs b/src/ImageSharp/Formats/Jxl/InlineArrays.cs
index f006d2fd0..627605170 100644
--- a/src/ImageSharp/Formats/Jxl/InlineArrays.cs
+++ b/src/ImageSharp/Formats/Jxl/InlineArrays.cs
@@ -7,6 +7,15 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jxl;
+///
+/// Used by Butteraugli
+///
+[InlineArray(2)]
+internal struct InlineArray2
+{
+ private T first;
+}
+
[InlineArray(3)]
internal struct InlineArray3
{
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Butteraugli/Butteraugli.cs b/src/ImageSharp/Formats/Jxl/Processing/Butteraugli/Butteraugli.cs
index c0322e91c..73420e86d 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/Butteraugli/Butteraugli.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/Butteraugli/Butteraugli.cs
@@ -42,11 +42,11 @@ internal static class Butteraugli
private const float GlobalScale =
1.0f / InternalGoodQualityThreshold;
- public static ReadOnlySpan Wmul =>
+ public static ReadOnlySpan Wmul =>
[
- 400.0, 1.50815703118, 0,
- 2150.0, 10.6195433239, 16.2176043152,
- 29.2353797994, 0.844626970982, 0.703646627719,
+ 400.0f, 1.50815703118f, 0f,
+ 2150.0f, 10.6195433239f, 16.2176043152f,
+ 29.2353797994f, 0.844626970982f, 0.703646627719f,
];
public static ReadOnlySpan ComputeKernel(float sigma)
@@ -55,7 +55,11 @@ internal static class Butteraugli
float scaler = -1.0f / (2.0f * sigma * sigma);
int diff = Math.Max(1, (int)(m * MathF.Abs(sigma)));
- // Use new because there's only up to 3 elements
+ // If sigma is very large we should not return a 'new float[]' allocation.
+ // This guard is temporary so we can verify the range of the number of elements.
+ // TODO: remove guard if the value doesn't exceed the limit for many JXL files
+ DebugGuard.MustBeLessThanOrEqualTo(sigma, 32f, nameof(sigma));
+
float[] kernel = new float[(2 * diff) + 1];
for (int i = -diff; i <= diff; i++)
@@ -536,7 +540,7 @@ internal static class Butteraugli
Configuration configuration,
in ButteraugliParameters parameters,
JxlImage3F mf,
- JxlImageF[] hf,
+ ref InlineArray2 hf,
BlurTemp blurTemp)
{
const float sigmaHf = 3.22489901262f;
@@ -2095,8 +2099,8 @@ internal static class Butteraugli
}
}
- JxlImageF[] hf0 = new JxlImageF[2];
- JxlImageF[] hf1 = new JxlImageF[2];
+ InlineArray2 hf0 = default;
+ InlineArray2 hf1 = default;
if (!SeparateMfAndHf(parameters, image0, hf0, blurTemp))
{
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsReader.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsReader.cs
index 78707b702..180dbd3c1 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsReader.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlAnsReader.cs
@@ -103,8 +103,7 @@ internal static class JxlAnsReader
if (isSimpleCode)
{
- Span symbols = stackalloc uint[2];
- symbols.Clear();
+ Span symbols = [0, 0];
uint maxSymbol = 0u;
uint symCount = reader.ReadBits32(1u) + 1u;
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlHuffmanDecoder.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlHuffmanDecoder.cs
index 216d5e779..7201a511a 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlHuffmanDecoder.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlHuffmanDecoder.cs
@@ -269,8 +269,9 @@ internal sealed class JxlHuffmanDecoder
return ReadSimpleCode(alphabetSize, br, this.Table);
}
- // The alphabet size is at most 256
- Span codeLengths = stackalloc byte[alphabetSize];
+ DebugGuard.MustBeLessThanOrEqualTo(alphabetSize, 256, nameof(alphabetSize));
+
+ Span codeLengths = stackalloc byte[256].Slice(0, alphabetSize);
codeLengths.Clear(); // Zero-initialized in reference software
Span codeLengthCodeLengths = stackalloc byte[CodeLengthCodes];
diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlDct.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlDct.cs
new file mode 100644
index 000000000..5e3e76355
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/JxlDct.cs
@@ -0,0 +1,305 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
+
+///
+/// Discrete Cosine Transform with SIMD support.
+///
+internal static class JxlDct
+{
+ ///
+ /// Creates a new coefficient bundle.
+ ///
+ /// Number of items.
+ /// Coefficient size.
+ /// A new coefficient bundle.
+ public static CoefficientBundle CoeffBundle(int n, int sz) => new(n, sz);
+
+ public static void Dct1DCore(int n, int sz, Span mem, Span tmp)
+ {
+ if (n == 2)
+ {
+ Vector in1 = new(mem);
+ Vector in2 = new(mem[sz..]);
+ (in1 + in2).CopyTo(mem);
+ (in1 - in2).CopyTo(mem[sz..]);
+ }
+ else
+ {
+ CoefficientBundle cb = CoeffBundle(n / 2, sz);
+
+ cb.AddReverse(mem, mem[(n / 2 * sz)..], tmp);
+ Dct1DCore(n / 2, sz, tmp, tmp[(n * sz)..]);
+
+ cb.SubReverse(mem, mem[(n / 2 * sz)..], tmp[(n / 2 * sz)..]);
+ cb.Multiply(tmp);
+
+ Dct1DCore(n / 2, sz, tmp[(n / 2 * sz)..], tmp[(n * sz)..]);
+ cb.B(tmp[(n / 2 * sz)..]);
+
+ CoeffBundle(n, sz).InverseEvenOdd(tmp, mem);
+ }
+ }
+
+ public static void InverseDct1DCore(int n, int sz, Span from, int fromStride, Span to, int toStride, Span tmp)
+ {
+ if (n == 1)
+ {
+ from.CopyTo(to);
+ }
+ else if (n == 2)
+ {
+ Vector in1 = new(from);
+ Vector in2 = new(from[fromStride..]);
+ (in1 + in2).CopyTo(to);
+ (in1 + in2).CopyTo(to[toStride..]);
+ }
+ else
+ {
+ CoefficientBundle cbDiv2 = CoeffBundle(n / 2, sz);
+ CoefficientBundle cb = CoeffBundle(n, sz);
+
+ cb.ForwardEvenOdd(from, fromStride, tmp);
+ InverseDct1DCore(n / 2, sz, tmp, sz, tmp, sz, tmp[(n * sz)..]);
+
+ cbDiv2.BTranspose(tmp[((n / 2) * sz)..]);
+ InverseDct1DCore(n / 2, sz, tmp[((n / 2) * sz)..], sz, tmp[((n / 2) * sz)..], sz, tmp[(n * sz)..]);
+
+ cb.MultiplyAndAdd(tmp, to, toStride);
+ }
+ }
+
+ public static void Dct1DWrapper(int n, int m, bool fit, JxlDctSource from, JxlDctOutput to, int mp, Span tmp)
+ {
+ CoefficientBundle cb = CoeffBundle(n, m);
+
+ for (int i = 0; i < mp; i += m)
+ {
+ cb.LoadFromBlock(from, i, tmp);
+ Dct1DCore(n, m, tmp, tmp[(n * m)..]);
+ cb.StoreToBlockAndScale(tmp, ref to, i);
+
+ if (fit)
+ {
+ return;
+ }
+ }
+ }
+
+ public static void InverseDct1DWrapper(int n, int m, bool fit, JxlDctSource from, JxlDctOutput to, int mp, Span tmp)
+ {
+ for (int i = 0; i < mp; i += m)
+ {
+ InverseDct1DCore(n, m, from.Address(0, i), from.Stride, to.Address(0, i), to.Stride, tmp);
+
+ if (fit)
+ {
+ return;
+ }
+ }
+ }
+
+ public static void Dct1DCapped(int n, int m, int l, JxlDctSource from, JxlDctOutput to, Span tmp)
+ {
+ bool fit = m <= l;
+ Dct1DWrapper(n, m, fit, from, to, m, tmp);
+ }
+
+ public static void InverseDct1DCapped(int n, int m, int l, JxlDctSource from, JxlDctOutput to, Span tmp)
+ {
+ bool fit = m <= l;
+ InverseDct1DWrapper(n, m, fit, from, to, m, tmp);
+ }
+
+ public static void Dct1D(int n, int m, JxlDctSource from, JxlDctOutput to, Span tmp)
+ {
+ int lanes = Vector.Count;
+ Dct1DCapped(n, m, lanes, from, to, tmp);
+ }
+
+ public static void InverseDct1D(int n, int m, JxlDctSource source, JxlDctOutput output, Span tmp)
+ {
+ int lanes = Vector.Count;
+ InverseDct1DCapped(n, m, lanes, source, output, tmp);
+ }
+
+ public static void ComputeScaledDct(int rows, int columns, JxlDctSource from, Span to, Span scratchSpace)
+ {
+ Span block = scratchSpace;
+ Span tmp = scratchSpace[(rows * columns)..];
+
+ if (rows < columns)
+ {
+ Dct1D(rows, columns, from, new JxlDctOutput(block, columns), tmp);
+ JxlTranspose.Transpose(rows, columns, new JxlDctSource(block, columns), new JxlDctOutput(to, rows));
+ Dct1D(columns, rows, new JxlDctSource(to, rows), new JxlDctOutput(block, rows), tmp);
+ JxlTranspose.Transpose(columns, rows, new JxlDctSource(block, rows), new JxlDctOutput(to, columns));
+ }
+ else
+ {
+ Dct1D(rows, columns, from, new JxlDctOutput(to, columns), tmp);
+ JxlTranspose.Transpose(rows, columns, new JxlDctSource(to, columns), new JxlDctOutput(block, rows));
+ Dct1D(columns, rows, new JxlDctSource(block, rows), new JxlDctOutput(to, rows), tmp);
+ }
+ }
+
+ public static void ComputeScaledInverseDct(int rows, int columns, Span from, JxlDctOutput to, Span scratchSpace)
+ {
+ Span block = scratchSpace;
+ Span tmp = scratchSpace[(rows * columns)..];
+
+ if (rows < columns)
+ {
+ JxlTranspose.Transpose(rows, columns, new JxlDctSource(from, columns), new JxlDctOutput(block, rows));
+ InverseDct1D(columns, rows, new JxlDctSource(block, rows), new JxlDctOutput(from, rows), tmp);
+ JxlTranspose.Transpose(columns, rows, new JxlDctSource(from, rows), new JxlDctOutput(block, columns));
+ InverseDct1D(rows, columns, new JxlDctSource(block, columns), to, tmp);
+ }
+ else
+ {
+ InverseDct1D(columns, rows, new JxlDctSource(from, rows), new JxlDctOutput(block, rows), tmp);
+ JxlTranspose.Transpose(columns, rows, new JxlDctSource(block, rows), new JxlDctOutput(from, columns));
+ InverseDct1D(rows, columns, new JxlDctSource(from, columns), to, tmp);
+ }
+ }
+
+ ///
+ /// Core methods for the Discrete Cosine Transform (DCT).
+ ///
+ public readonly struct CoefficientBundle(int n, int sz)
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddReverse(Span aIn1, Span aIn2, Span aOut)
+ {
+ for (int i = 0; i < n; i++)
+ {
+ Vector in1 = new(aIn1[(i * sz)..]);
+ Vector in2 = new(aIn2[((n - i - 1) * sz)..]);
+ (in1 + in2).CopyTo(aOut[(i * sz)..]);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void SubReverse(Span aIn1, Span aIn2, Span aOut)
+ {
+ for (int i = 0; i < n; i++)
+ {
+ Vector in1 = new(aIn1[(i * sz)..]);
+ Vector in2 = new(aIn2[((n - i - 1) * sz)..]);
+ (in1 - in2).CopyTo(aOut[(i * sz)..]);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void B(Span coeff)
+ {
+ Vector sqrt2 = new(JxlDctScales.Sqrt2);
+ Vector in10 = new(coeff);
+ Vector in20 = new(coeff[sz..]);
+ ((in10 * sqrt2) + in20).CopyTo(coeff);
+
+ for (int i = 1; i + 1 < n; i++)
+ {
+ Vector in1 = new(coeff[(i * sz)..]);
+ Vector in2 = new(coeff[((i + 1) * sz)..]);
+ (in1 + in2).CopyTo(coeff[(i * sz)..]);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void BTranspose(Span coeff)
+ {
+ for (int i = n - 1; i > 0; i--)
+ {
+ Vector in1 = new(coeff[(i * sz)..]);
+ Vector in2 = new(coeff[((i - 1) * sz)..]);
+ (in1 + in2).CopyTo(coeff[(i * sz)..]);
+ }
+
+ Vector sqrt2 = new(JxlDctScales.Sqrt2);
+ Vector in1x = new(coeff);
+ (in1x * sqrt2).CopyTo(coeff);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void InverseEvenOdd(Span aIn, Span aOut)
+ {
+ for (int i = 0; i < n / 2; i++)
+ {
+ new Vector(aIn[(i * sz)..]).CopyTo(aOut[((2 * i) * sz)..]);
+ }
+
+ for (int i = n / 2; i < n; i++)
+ {
+ new Vector(aIn[(i * sz)..]).CopyTo(aOut[(((2 * (i - (n / 2))) + 1) * sz)..]);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void ForwardEvenOdd(Span aIn, int aInStride, Span aOut)
+ {
+ for (int i = 0; i < n / 2; i++)
+ {
+ new Vector(aIn[(2 * i * aInStride)..]).CopyTo(aOut[(i * sz)..]);
+ }
+
+ for (int i = n / 2; i < n; i++)
+ {
+ new Vector(aIn[(((2 * (i - (n / 2))) + 1) * aInStride)..]).CopyTo(aOut[(i * sz)..]);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Multiply(Span coeff)
+ {
+ ReadOnlySpan multipliers = JxlDctScales.GetMultipliers(n);
+
+ for (int i = 0; i < n / 2; i++)
+ {
+ Vector in1 = new(coeff[(((n / 2) + i) * sz)..]);
+ Vector mul = new(multipliers[i]);
+ (in1 * mul).CopyTo(coeff[((n / (2 + i)) * sz)..]);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void MultiplyAndAdd(Span coeff, Span output, int outStride)
+ {
+ ReadOnlySpan multipliers = JxlDctScales.GetMultipliers(n);
+
+ for (int i = 0; i < n / 2; i++)
+ {
+ Vector mul = new(multipliers[i]);
+ Vector in1 = new(coeff[(i * sz)..]);
+ Vector in2 = new(coeff[((n / (2 + i)) * sz)..]);
+ Vector out1 = (mul * in2) * in1;
+ Vector out2 = -(mul * in2) + in1;
+ out1.CopyTo(output[(i * outStride)..]);
+ out2.CopyTo(output[((n - i - 1) * outStride)..]);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void LoadFromBlock(in JxlDctSource input, int offset, Span coeff)
+ {
+ for (int i = 0; i < n; i++)
+ {
+ input.LoadPart(i, offset).CopyTo(coeff[(i * sz)..]);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void StoreToBlockAndScale(Span coeff, ref JxlDctOutput output, int offset)
+ {
+ Vector mul = new(1.0f / n);
+ for (int i = 0; i < n; i++)
+ {
+ output.StorePart(mul * new Vector(coeff[(i * sz)..]), i, offset);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlDctOutput.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlDctOutput.cs
new file mode 100644
index 000000000..1be3b9adf
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/JxlDctOutput.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
+
+///
+/// Output DCT block.
+///
+internal ref struct JxlDctOutput(Span data, int stride)
+{
+ ///
+ /// Raw block data.
+ ///
+ public Span Data = data;
+
+ ///
+ /// Stride size.
+ ///
+ public readonly int Stride = stride;
+
+ ///
+ /// Returns the span to the start of a row and offset.
+ ///
+ /// The row index.
+ /// The offset.
+ ///
+ /// Span for that row & offset.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly Span Address(int row, int i) => this.Data[((row * this.Stride) + i)..];
+
+ ///
+ /// Writes a single value to the block at the row and offset.
+ ///
+ /// The value to write.
+ /// The row index.
+ /// The offset.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Write(float value, int row, int i) => this.Data[(row * this.Stride) + i] = value;
+
+ ///
+ /// Stores the vector into the data at the specified row and offset.
+ ///
+ /// The vector to write.
+ /// The row index.
+ /// The offset.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void StorePart(Vector value, int row, int index) => value.CopyTo(this.Address(row, index));
+}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlDctScales.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlDctScales.cs
index 26bc7ee8e..0ebcb7a1f 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/JxlDctScales.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/JxlDctScales.cs
@@ -9,6 +9,16 @@ namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
///
internal static class JxlDctScales
{
+ ///
+ /// Square root of 2.
+ ///
+ public const float Sqrt2 = 1.41421356237f;
+
+ ///
+ /// Square root of 0.5.
+ ///
+ public const float Sqrt05 = 0.70710678118f;
+
///
/// Gets 8x1 DCT resample scales.
///
@@ -358,4 +368,21 @@ internal static class JxlDctScales
9.058751453879703f, 11.644627325175037f, 16.300023088031555f,
27.163977662448232f, 81.48784219222516f,
];
+
+ ///
+ /// Returns DCT multipliers for size .
+ ///
+ /// The multiplier size
+ /// DCT multipliers for the given size.
+ public static ReadOnlySpan GetMultipliers(int n) => n switch
+ {
+ 4 => Multipliers4,
+ 8 => Multipliers8,
+ 16 => Multipliers16,
+ 32 => Multipliers32,
+ 64 => Multipliers64,
+ 128 => Multipliers128,
+ 256 => Multipliers256,
+ _ => []
+ };
}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlDctSource.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlDctSource.cs
new file mode 100644
index 000000000..08dafc11e
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/JxlDctSource.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
+
+///
+/// Source DCT block.
+///
+internal readonly ref struct JxlDctSource(Span data, int stride)
+{
+ ///
+ /// Raw block data.
+ ///
+ public readonly Span Data = data;
+
+ ///
+ /// Stride size.
+ ///
+ public readonly int Stride = stride;
+
+ ///
+ /// Returns the span to the start of a row and offset.
+ ///
+ /// The row index.
+ /// The offset.
+ ///
+ /// Span for that row & offset.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span Address(int row, int i) => this.Data[((row * this.Stride) + i)..];
+
+ ///
+ /// Returns the coefficient at the row and offset.
+ ///
+ /// The row index.
+ /// The offset.
+ ///
+ /// Coefficient at that row and offset.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Read(int row, int i) => this.Data[(row * this.Stride) + i];
+
+ ///
+ /// Loads a vector at the specified row and offset.
+ ///
+ /// The row index.
+ /// The offset.
+ ///
+ /// Vector at that row and offset.
+ ///
+ public Vector LoadPart(int row, int i) => new(this.Address(row, i));
+}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlImageOperations.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlImageOperations.cs
index b356b9aab..e5ddf834f 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/JxlImageOperations.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/JxlImageOperations.cs
@@ -346,16 +346,20 @@ internal static class JxlImageOperations
{
DebugGuard.MustBeGreaterThan(xSize, 0, nameof(xSize));
- while (x < 0 || x >= xSize)
+ while (true)
{
if (x < 0)
{
x = -x - 1;
}
- else
+ else if (x >= xSize)
{
x = (2 * xSize) - 1 - x;
}
+ else
+ {
+ break;
+ }
}
return (int)x;
diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlShared.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlShared.cs
new file mode 100644
index 000000000..711dbc27e
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/JxlShared.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
+
+///
+/// Shared JPEG XL constants
+///
+internal static class JxlShared
+{
+ ///
+ /// Maximum number of passes in an image.
+ ///
+ public const int MaximumNumberOfPasses = 11;
+
+ ///
+ /// Maximum number of reference frames.
+ ///
+ public const int MaximumNumberOfReferenceFrames = 4;
+
+ ///
+ /// Gets the 12-byte signature (a.k.a. magic) for JPEG XL files.
+ ///
+ public static ReadOnlySpan SignatureBox =>
+ [
+ 0x00, 0x00, 0x00, 0x0C,
+ (byte)'J', (byte)'X', (byte)'L', (byte)' ',
+ 0x0D, 0x0A, 0x87, 0x0A
+ ];
+}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlTranspose.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlTranspose.cs
new file mode 100644
index 000000000..4b05150a7
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/JxlTranspose.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
+
+///
+/// Performs transpose on JPEG XL DCT blocks.
+///
+internal static class JxlTranspose
+{
+ // TODO: SIMD
+ public static void Transpose(int r, int c, JxlDctSource from, JxlDctOutput to)
+ {
+ for (int n = 0; n < r; n++)
+ {
+ for (int m = 0; m < c; m++)
+ {
+ to.Write(from.Read(n, m), m, n);
+ }
+ }
+ }
+}