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); + } + } + } +}