Browse Source

Complete modular transforms, context prediction

Common/Helpers

- Add InterleaveLower and InterleaveUpper to Vector128_ and Vector256_
- Add unit test for InterleaveLower and InterleaveUpper (specifically for Vector256_)
- Add Average to Numerics.cs

Common

- Add 32 and 33 to the InlineArray.tt text template

Formats/Jxl/IO/Metadata

- Remove unnecessary System.Runtime.CompilerServices using directive from JxlCustomTransformData and JxlOpsinInvreseMatrix

Formats/Jxl/Processing/Decoder

- Remove unncessary using SixLabors.ImageSharp.Formats.Jxl.IO

Formats/Jxl/Processing/Encoder

- Add partial Fast Lossless Encoder work (+enc_fast_lossless.cc; largest file in libjxl source)
- Add linear algebra (+enc_linalg.cc, +enc_linalg.h)

Formats/Jxl/Processing/Jpeg

- Work that would later become JXL<->JPEG lossless coding mode

Formats/Jxl/Processing/Modular/Encoding/ContextPrediction

- Finish context prediction (+context_predict.h)

Formats/Jxl/Processing/Modular/Transforms

- Finish Reversible Color Transform (+rct.cc, +rct.h, +enc_rct.cc, +enc_rct.h)
- Finish Palette/Indexed coding (+palette.cc, +palette.h, +enc_palette.cc, enc_palette.h)
- Finish Squeeze transform (+squeeze.cc, +squeeze.h, +enc_squeeze.cc, +enc_squeeze.h)

Formats/Jxl/Processing/RenderPipeline

- Incomplete render pipeline abstractions with EPF (Edge Preserving Filter) 0 stage (+render_pipeline_stage.cc, +render_pipeline_stage.h, +stage_epf.cc, +stage_epf.h)

Formats/Jxl/Processing/Splines

- Remove unnecessary System.Runtime.CompilerServices using directive

Formats/Jxl/Processing

- Add dequantizer matrices
- Remove JxlEndianness (prefer ByteOrder from ImageSharp/Common)
- Add missing constant to JxlLoopFilter
- Remove unnecessary using SixLabors.ImageSharp.Common.Helpers from JxlMath
- Replace JxlPixelFormat to use ByteOrder
- Update quantizers to use dequantizer matrices and quantizer weights
- Add quantizer encoding and constants
- Add SIMD utilities
- Remove System.Runtime.CompilerServices using from JxlWeightsSeparable5
- Remove InlineArray3, InlineArray36 and InlineArray15 from InlineArrays (3 and 15 already exist in System.Runtime.CompilerServices; 36 already exists in InlineArray.tt from ImageSharp/Common)

NEXT STEPS

The current focus would be applying refactors and optimizations from reviews, followed by completing the JPEG XL modular.
pull/3153/head
winscripter 2 days ago
parent
commit
acc474ea35
  1. 9
      src/ImageSharp/Common/Helpers/Numerics.cs
  2. 40
      src/ImageSharp/Common/Helpers/Vector128Utilities.cs
  3. 166
      src/ImageSharp/Common/Helpers/Vector256Utilities.cs
  4. 22
      src/ImageSharp/Common/InlineArray.cs
  5. 2
      src/ImageSharp/Common/InlineArray.tt
  6. 1
      src/ImageSharp/Formats/Jxl/IO/Metadata/JxlCustomTransformData.cs
  7. 1
      src/ImageSharp/Formats/Jxl/IO/Metadata/JxlOpsinInverseMatrix.cs
  8. 33
      src/ImageSharp/Formats/Jxl/InlineArrays.cs
  9. 1
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlBoxContentDecoder.cs
  10. 1365
      src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlFastLosslessEncoder.cs
  11. 52
      src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlLinearAlgebra.cs
  12. 30
      src/ImageSharp/Formats/Jxl/Processing/Jpeg/JpegAppMarkerType.cs
  13. 265
      src/ImageSharp/Formats/Jxl/Processing/JxlDequantMatrices.cs
  14. 25
      src/ImageSharp/Formats/Jxl/Processing/JxlEndianness.cs
  15. 5
      src/ImageSharp/Formats/Jxl/Processing/JxlLoopFilter.cs
  16. 53
      src/ImageSharp/Formats/Jxl/Processing/JxlMath.cs
  17. 2
      src/ImageSharp/Formats/Jxl/Processing/JxlPixelFormat.cs
  18. 512
      src/ImageSharp/Formats/Jxl/Processing/JxlQuantWeights.cs
  19. 12
      src/ImageSharp/Formats/Jxl/Processing/JxlQuantizer.cs
  20. 46
      src/ImageSharp/Formats/Jxl/Processing/JxlQuantizerConstants.cs
  21. 22
      src/ImageSharp/Formats/Jxl/Processing/JxlQuantizerEncoding.cs
  22. 133
      src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.StoreInterleaved.Generated.cs
  23. 44
      src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.StoreInterleaved.tt
  24. 104
      src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.cs
  25. 2
      src/ImageSharp/Formats/Jxl/Processing/JxlWeightsSeparable5.cs
  26. 390
      src/ImageSharp/Formats/Jxl/Processing/Modular/Encoding/ContextPrediction/JxlContextPrediction.cs
  27. 13
      src/ImageSharp/Formats/Jxl/Processing/Modular/Encoding/ContextPrediction/JxlPredictionResult.cs
  28. 35
      src/ImageSharp/Formats/Jxl/Processing/Modular/Encoding/ContextPrediction/JxlPredictorMode.cs
  29. 24
      src/ImageSharp/Formats/Jxl/Processing/Modular/JxlModularChannel.cs
  30. 10
      src/ImageSharp/Formats/Jxl/Processing/Modular/JxlModularImage.cs
  31. 1227
      src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlPalette.cs
  32. 1
      src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlRct.cs
  33. 793
      src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlSqueeze.cs
  34. 18
      src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlSqueezeParameters.cs
  35. 25
      src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlTransform.cs
  36. 109
      src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/Epf0Stage.cs
  37. 14
      src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/EpfStageType.cs
  38. 22
      src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/EpfUtils.cs
  39. 30
      src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/RenderPipelineChannelMode.cs
  40. 92
      src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/RenderPipelineStageBase.cs
  41. 21
      src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/RenderPipelineStageConfiguration.cs
  42. 2
      src/ImageSharp/Formats/Jxl/Processing/Splines/JxlSplineSegment.cs
  43. 14
      src/ImageSharp/ImageSharp.csproj
  44. 48
      tests/ImageSharp.Tests/Common/Vector256UtilitiesTests.cs

9
src/ImageSharp/Common/Helpers/Numerics.cs

@ -1033,4 +1033,13 @@ internal static class Numerics
public static nuint Vector512Count<TVector>(int length)
where TVector : struct
=> (uint)length / (uint)Vector512<TVector>.Count;
/// <summary>
/// Computes the average of two integers.
/// </summary>
/// <param name="x">First integer</param>
/// <param name="y">Second integer</param>
/// <returns>The average of x, y.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Average(int x, int y) => (x + y + ((x > y) ? 1 : 0)) >> 1;
}

40
src/ImageSharp/Common/Helpers/Vector128Utilities.cs

@ -859,4 +859,44 @@ internal static class Vector128_
Vector128<sbyte> unpacked = Vector128.Create(left.GetLower(), right.GetLower());
return Vector128.ShuffleNative(unpacked, Vector128.Create(0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15));
}
/// <summary>
/// Interleaves the lower half of the vector.
/// </summary>
/// <param name="a">First vector</param>
/// <param name="b">Second vector</param>
/// <returns>
/// <c>{ a[0], b[0], a[1], b[1] }</c>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<int> InterleaveLower(Vector128<int> a, Vector128<int> b)
{
Vector128<int> shuffledA = Vector128.Shuffle(a, Vector128.Create(0, 0, 1, 1));
Vector128<int> shuffledB = Vector128.Shuffle(b, Vector128.Create(0, 0, 1, 1));
Vector128<int> maskA = Vector128.Create(-1, 0, -1, 0);
Vector128<int> maskB = Vector128.Create(0, -1, 0, -1);
return (shuffledA & maskA) | (shuffledB & maskB);
}
/// <summary>
/// Interleaves the upper half of the vector.
/// </summary>
/// <param name="a">First vector</param>
/// <param name="b">Second vector</param>
/// <returns>
/// <c>{ a[2], b[2], a[3], b[3] }</c>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<int> InterleaveUpper(Vector128<int> a, Vector128<int> b)
{
Vector128<int> shuffledA = Vector128.Shuffle(a, Vector128.Create(2, 2, 3, 3));
Vector128<int> shuffledB = Vector128.Shuffle(b, Vector128.Create(2, 2, 3, 3));
Vector128<int> maskA = Vector128.Create(-1, 0, -1, 0);
Vector128<int> maskB = Vector128.Create(0, -1, 0, -1);
return (shuffledA & maskA) | (shuffledB & maskB);
}
}

166
src/ImageSharp/Common/Helpers/Vector256Utilities.cs

@ -397,4 +397,170 @@ internal static class Vector256_
return Vector256.Create(lo, hi);
}
/// <summary>
/// Multiplies only the even indices of the two 256-bit vectors,
/// producing half as many elements of twice the element width.
/// </summary>
/// <param name="left">Left vector to multiply.</param>
/// <param name="right">Right vector to multiply</param>
/// <returns>
/// <code>
/// {
/// A[0] * B[0],
/// A[2] * B[2],
/// A[4] * B[4],
/// A[6] * B[6]
/// }
/// </code>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<long> MultiplyEven(Vector256<int> left, Vector256<int> right)
=> Vector256.Create(
left[0] * right[0],
left[2] * right[2],
left[4] * right[4],
left[6] * right[6]);
/// <summary>
/// Multiplies only the odd indices of the two 256-bit vectors,
/// producing half as many elements of twice the element width.
/// </summary>
/// <param name="left">Left vector to multiply.</param>
/// <param name="right">Right vector to multiply</param>
/// <returns>
/// <code>
/// {
/// A[1] * B[1],
/// A[3] * B[3],
/// A[5] * B[5],
/// A[7] * B[7]
/// }
/// </code>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<long> MultiplyOdd(Vector256<int> left, Vector256<int> right)
=> Vector256.Create(
left[1] * right[1],
left[3] * right[3],
left[5] * right[5],
left[7] * right[7]);
/// <summary>
/// Produces a vector by interleaving the even-indexed elements
/// of the left and right vectors.
/// </summary>
/// <param name="left">Left vector to interleave.</param>
/// <param name="right">Right vector to interleave.</param>
/// <returns>
/// <code>
/// {
/// A[0], B[0],
/// A[2], B[2],
/// A[4], B[4],
/// A[6], B[6]
/// }
/// </code>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<int> InterleaveEven(
Vector256<int> left,
Vector256<int> right)
=> Vector256.Create(
left[0],
right[0],
left[2],
right[2],
left[4],
right[4],
left[6],
right[6]);
/// <summary>
/// Produces a vector by interleaving the odd-indexed elements
/// of the left and right vectors.
/// </summary>
/// <param name="left">Left vector to interleave.</param>
/// <param name="right">Right vector to interleave.</param>
/// <returns>
/// <code>
/// {
/// A[1], B[1],
/// A[3], B[3],
/// A[5], B[5],
/// A[7], B[7]
/// }
/// </code>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<int> InterleaveOdd(
Vector256<int> left,
Vector256<int> right)
=> Vector256.Create(
left[1],
right[1],
left[3],
right[3],
left[5],
right[5],
left[7],
right[7]);
/// <summary>
/// Produces a vector with masks where 0xFFFFFFFF specifies
/// that the left value does not equal to the right value and
/// 0x00000000 specifies that the value equals to the
/// right value.
/// </summary>
/// <param name="left">Left vector to compare for inequality.</param>
/// <param name="right">Right vector to compare for inequality.</param>
/// <returns>
/// 0xFFFFFFFF for values that aren't equal, 0x00000000 for
/// values that are equal. This is essentially the inverse of
/// <see cref="Vector256.Equals{T}(Vector256{T}, Vector256{T})"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<int> NotEqual(
Vector256<int> left,
Vector256<int> right) => ~Vector256.Equals(left, right);
/// <summary>
/// Interleaves the lower half of the vector.
/// </summary>
/// <param name="a">First vector</param>
/// <param name="b">Second vector</param>
/// <returns>
/// <c>{ a[0], b[0], a[1], b[1], a[2], b[2], a[3], b[3] }</c>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<int> InterleaveLower(Vector256<int> a, Vector256<int> b)
{
Vector256<int> shuffledA = Vector256.Shuffle(a, Vector256.Create(0, 0, 1, 1, 2, 2, 3, 3));
Vector256<int> shuffledB = Vector256.Shuffle(b, Vector256.Create(0, 0, 1, 1, 2, 2, 3, 3));
Vector256<int> maskA = Vector256.Create(-1, 0, -1, 0, -1, 0, -1, 0);
Vector256<int> maskB = Vector256.Create(0, -1, 0, -1, 0, -1, 0, -1);
return (shuffledA & maskA) | (shuffledB & maskB);
}
/// <summary>
/// Interleaves the upper half of the vector.
/// </summary>
/// <param name="a">First vector</param>
/// <param name="b">Second vector</param>
/// <returns>
/// <c>{ a[4], b[4], a[5], b[5], a[6], b[6], a[7], b[7] }</c>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<int> InterleaveUpper(Vector256<int> a, Vector256<int> b)
{
Vector256<int> shuffledA = Vector256.Shuffle(a, Vector256.Create(4, 4, 5, 5, 6, 6, 7, 7));
Vector256<int> shuffledB = Vector256.Shuffle(b, Vector256.Create(4, 4, 5, 5, 6, 6, 7, 7));
Vector256<int> maskA = Vector256.Create(-1, 0, -1, 0, -1, 0, -1, 0);
Vector256<int> maskB = Vector256.Create(0, -1, 0, -1, 0, -1, 0, -1);
return (shuffledA & maskA) | (shuffledB & maskB);
}
}

22
src/ImageSharp/Common/InlineArray.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
// <auto-generated />
@ -71,6 +71,24 @@ internal struct InlineArray26<T>
private T t;
}
/// <summary>
/// Represents a safe, fixed sized buffer of 32 elements.
/// </summary>
[InlineArray(32)]
internal struct InlineArray32<T>
{
private T t;
}
/// <summary>
/// Represents a safe, fixed sized buffer of 33 elements.
/// </summary>
[InlineArray(33)]
internal struct InlineArray33<T>
{
private T t;
}
/// <summary>
/// Represents a safe, fixed sized buffer of 36 elements.
/// </summary>
@ -88,3 +106,5 @@ internal struct InlineArray256<T>
{
private T t;
}

2
src/ImageSharp/Common/InlineArray.tt

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp;
<#GenerateInlineArrays();#>
<#+
private static int[] Lengths = [4, 8, 14, 16, 18, 19, 26, 36, 256];
private static int[] Lengths = [4, 8, 14, 16, 18, 19, 26, 32, 33, 36, 256];
void GenerateInlineArrays()
{

1
src/ImageSharp/Formats/Jxl/IO/Metadata/JxlCustomTransformData.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jxl.Fields;
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata;

1
src/ImageSharp/Formats/Jxl/IO/Metadata/JxlOpsinInverseMatrix.cs

@ -3,6 +3,7 @@
#pragma warning disable SA1401 // Fields should be private
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jxl.Fields;
using SixLabors.ImageSharp.Formats.Jxl.Processing;

33
src/ImageSharp/Formats/Jxl/InlineArrays.cs

@ -7,30 +7,6 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jxl;
[InlineArray(3)]
internal struct InlineArray3<T>
{
private T first;
}
/// <summary>
/// Used by JxlOpsinParameters
/// </summary>
[InlineArray(36)]
internal struct InlineArray36<T>
{
private T first;
}
/// <summary>
/// Used by JxlCustomTransformData
/// </summary>
[InlineArray(15)]
internal struct InlineArray15<T>
{
private T first;
}
/// <summary>
/// Used by JxlCustomTransformData
/// </summary>
@ -48,12 +24,3 @@ internal struct InlineArray210<T>
{
private T first;
}
/// <summary>
/// Used by JxlWeightsSeparable5
/// </summary>
[InlineArray(12)]
internal struct InlineArray12<T>
{
private T first;
}

1
src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlBoxContentDecoder.cs

@ -3,7 +3,6 @@
using System.Buffers;
using System.IO.Compression;
using SixLabors.ImageSharp.Formats.Jxl.IO;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder;

1365
src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlFastLosslessEncoder.cs

File diff suppressed because it is too large

52
src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlLinearAlgebra.cs

@ -0,0 +1,52 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using Matrix2x2 = System.Runtime.CompilerServices.InlineArray2<System.Runtime.CompilerServices.InlineArray2<double>>;
using Vector2 = System.Runtime.CompilerServices.InlineArray2<double>;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder;
/// <summary>
/// Handles linear algebra for encoding.
/// </summary>
internal static class JxlLinearAlgebra
{
public static void ConvertToDiagonal(Matrix2x2 a, Vector2 diag, Matrix2x2 u)
{
DebugGuard.MustBeLessThan(Math.Abs(a[0][1] - a[1][0]), 1e-15, nameof(a));
double b = -(a[0][0] + a[1][1]);
double c = (a[0][0] * a[1][1]) - (a[0][1] * a[0][1]);
double d = (b * b) - (4.0 * c);
if (Math.Abs(a[0][1]) < 1e-10 || d < 0)
{
// Already diagonal.
diag[0] = a[0][0];
diag[1] = a[1][1];
u[0][0] = u[1][1] = 1.0;
u[0][1] = u[1][0] = 0.0;
return;
}
double sqd = Math.Sqrt(d);
double l1 = (-b - sqd) * 0.5;
double l2 = (-b + sqd) * 0.5;
Vector2 v1 = default;
v1[0] = a[0][0] - l1;
v1[1] = a[1][0];
double v1n = 1.0 / JxlMath.Hypot(v1[0], v1[1]);
v1[0] = v1[0] * v1n;
v1[1] = v1[1] * v1n;
diag[0] = l1;
diag[1] = l2;
u[0][0] = v1[1];
u[0][1] = -v1[0];
u[1][0] = v1[0];
u[1][1] = v1[1];
}
}

30
src/ImageSharp/Formats/Jxl/Processing/Jpeg/JpegAppMarkerType.cs

@ -0,0 +1,30 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Jpeg;
/// <summary>
/// Identifies the kind of APP marker in a JPEG file.
/// </summary>
internal enum JpegAppMarkerType : byte
{
/// <summary>
/// Unknown APP marker
/// </summary>
Unknown,
/// <summary>
/// Contains ICC profile metadata
/// </summary>
Icc,
/// <summary>
/// Contains EXIF profile metadata
/// </summary>
Exif,
/// <summary>
/// Contains XMP profile metadata
/// </summary>
Xmp
}

265
src/ImageSharp/Formats/Jxl/Processing/JxlDequantMatrices.cs

@ -0,0 +1,265 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
/// <summary>
/// Contains matrices used to inverse quantize coefficients.
/// </summary>
internal sealed class JxlDequantMatrices
{
/// <summary>
/// Sum(DotProduct(RequiredSizeX, RequiredSizeY)).
/// </summary>
private const int SumRequiredXY = 2056;
private const int TotalTableSize = SumRequiredXY * JxlFrameDimensions.DctBlockSize * 3;
/// <summary>
/// Contains weights &amp; multipliers for transforms used by the codec (e.g. DCT, identity, AFV).
/// </summary>
public static readonly JxlQuantizerEncoding[] Library = GetLibrary();
private uint computedMask;
/// <summary>
/// Storage for quantization.
/// </summary>
private readonly Memory<byte> tableStorage;
/// <summary>
/// Contains matrices for forward quantization.
/// </summary>
private readonly Memory<float> table;
/// <summary>
/// Contains matrices for inverse quantization.
/// </summary>
private readonly Memory<float> inverseTable;
/// <summary>
/// Quantization table for DC
/// </summary>
private InlineArray3<float> dcQuant;
/// <summary>
/// Inverse quantization table for DC
/// </summary>
private InlineArray3<float> inverseDcQuant;
/// <summary>
/// Table offsets.
/// </summary>
private readonly int[] tableOffsets = new int[JxlAcStrategy.NumberOfValidStrategies * 3];
/// <summary>
/// Quantizer encodings. Multiple may be used depending on the kind of transform.
/// </summary>
private JxlQuantizerEncoding[] encodings = [];
/// <summary>
/// Initializes a new instance of the <see cref="JxlDequantMatrices"/> class.
/// </summary>
public JxlDequantMatrices()
{
// float dc_quant_[3] = {kDCQuant[0], kDCQuant[1], kDCQuant[2]};
// float inv_dc_quant_[3] = {kInvDCQuant[0], kInvDCQuant[1], kInvDCQuant[2]};
this.dcQuant[0] = JxlQuantizerConstants.DcQuant[0];
this.dcQuant[1] = JxlQuantizerConstants.DcQuant[1];
this.dcQuant[2] = JxlQuantizerConstants.DcQuant[2];
this.inverseDcQuant[0] = JxlQuantizerConstants.InverseDcQuant[0];
this.inverseDcQuant[1] = JxlQuantizerConstants.InverseDcQuant[1];
this.inverseDcQuant[2] = JxlQuantizerConstants.InverseDcQuant[2];
this.encodings = new JxlQuantizerEncoding[JxlQuantizerConstants.NumberOfQuantizerTables];
for (int i = 0; i < this.encodings.Length; i++)
{
this.encodings[i] = JxlQuantizerEncoding.Library(0);
}
int pos = 0;
Span<int> offsets = stackalloc int[JxlQuantizerConstants.NumberOfQuantizerTables * 3];
for (int i = 0; i < JxlQuantizerConstants.NumberOfQuantizerTables; i++)
{
int numBlocks = RequiredSizeX[i] * RequiredSizeY[i];
int num = numBlocks * JxlFrameDimensions.DctBlockSize;
int i3 = 3 * i;
for (int c = 0; c < 3; c++)
{
offsets[i3 + c] = pos + (c * num);
}
pos += 3 * num;
}
for (int i = 0; i < JxlAcStrategy.NumberOfValidStrategies; i++)
{
for (int c = 0; c < 3; c++)
{
this.tableOffsets[(i * 3) + c] = offsets[((int)JxlQuantizerConstants.AcStrategyToQuantTableMap[i] * 3) + c];
}
}
}
/// <summary>
/// Gets a lookup which represents required widths for each quantizer.
/// </summary>
private static ReadOnlySpan<int> RequiredSizeX => [1, 1, 1, 1, 2, 4, 1, 1, 2, 1, 1, 8, 4, 16, 8, 32, 16];
/// <summary>
/// Gets a lookup which represents required heights for each quantizer.
/// </summary>
private static ReadOnlySpan<int> RequiredSizeY => [1, 1, 1, 1, 2, 4, 2, 4, 4, 1, 1, 8, 8, 16, 16, 32, 32];
/// <summary>
/// Returns the default library with quantizer encodings for all transforms
/// used by the JPEG XL codec.
/// </summary>
/// <returns>Encodings for all kinds of transforms.</returns>
/// <exception cref="InvalidOperationException">Used when quantization constants were partially updated.</exception>
public static JxlQuantizerEncoding[] GetLibrary()
{
if (JxlQuantizerConstants.NumberOfQuantizerTables != 17)
{
throw new InvalidOperationException("This function should be updated when adding new quantization types");
}
if (JxlQuantWeights.NumPredefinedTables != 1)
{
throw new InvalidOperationException("This function should be updated when adding new quantization matrices to the library");
}
Verify(0, JxlQuantTable.DCT);
Verify(1, JxlQuantTable.IDENTITY);
Verify(2, JxlQuantTable.DCT2X2);
Verify(3, JxlQuantTable.DCT4X4);
Verify(4, JxlQuantTable.DCT16X16);
Verify(5, JxlQuantTable.DCT32X32);
Verify(6, JxlQuantTable.DCT8X16);
Verify(7, JxlQuantTable.DCT8X32);
Verify(8, JxlQuantTable.DCT16X32);
Verify(9, JxlQuantTable.DCT4X8);
Verify(10, JxlQuantTable.AFV0);
Verify(11, JxlQuantTable.DCT64X64);
Verify(12, JxlQuantTable.DCT32X64);
Verify(13, JxlQuantTable.DCT128X128);
Verify(14, JxlQuantTable.DCT64X128);
Verify(15, JxlQuantTable.DCT256X256);
Verify(16, JxlQuantTable.DCT128X256);
return
[
JxlQuantWeights.Dct,
JxlQuantWeights.Identity,
JxlQuantWeights.Dct2x2,
JxlQuantWeights.Dct4x4,
JxlQuantWeights.Dct16x16,
JxlQuantWeights.Dct32x32,
JxlQuantWeights.Dct8x16,
JxlQuantWeights.Dct8x32,
JxlQuantWeights.Dct16x32,
JxlQuantWeights.Dct4x8,
JxlQuantWeights.Afv,
JxlQuantWeights.Dct64x64,
JxlQuantWeights.Dct32x32,
JxlQuantWeights.Dct128x128,
JxlQuantWeights.Dct64x128,
JxlQuantWeights.Dct256x256,
JxlQuantWeights.Dct128x256
];
[Conditional("DEBUG")]
static void Verify(int expected, JxlQuantTable actual)
{
if (expected != (byte)actual)
{
throw new InvalidOperationException("Quantizer modes were partially updated; this method needs to be updated too");
}
}
}
/// <summary>
/// Returns a matrix for the specified kind of quantizer and index.
/// </summary>
/// <param name="quantKind">Quantizer kind</param>
/// <param name="c">Index</param>
/// <returns>Matrix</returns>
public Span<float> GetMatrix(JxlAcStrategyType quantKind, int c)
{
DebugGuard.MustBeGreaterThan((1 << (int)quantKind) & this.computedMask, 0, nameof(quantKind));
return this.table.Span[this.tableOffsets[((int)quantKind * 3) + c]..];
}
/// <summary>
/// Returns an inverse matrix for the specified kind of quantizer and index.
/// </summary>
/// <param name="quantKind">Quantizer kind</param>
/// <param name="c">Index</param>
/// <returns>Inverse matrix</returns>
public Span<float> GetInverseMatrix(JxlAcStrategyType quantKind, int c)
{
DebugGuard.MustBeGreaterThan((1 << (int)quantKind) & this.computedMask, 0, nameof(quantKind));
return this.inverseTable.Span[this.tableOffsets[((int)quantKind * 3) + c]..];
}
/// <summary>
/// Returns a DC quant for index c.
/// </summary>
/// <param name="c">The DC quantizer index.</param>
/// <returns>DC quant for index <paramref name="c"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float GetDcQuant(int c) => this.dcQuant[c];
/// <summary>
/// Returns all DC quantizers. See also <seealso cref="GetDcQuant(int)"/>.
/// </summary>
/// <returns>Span that covers all DC quantizers.</returns>
public Span<float> GetDcQuants() => this.dcQuant;
/// <summary>
/// Returns an inverse DC quant for index c.
/// </summary>
/// <param name="c">The inverse DC quantizer index.</param>
/// <returns>Inverse DC quant for index <paramref name="c"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float GetInverseDcQuant(int c) => this.inverseDcQuant[c];
/// <summary>
/// Applies the specified DC quantizer.
/// </summary>
/// <param name="dc">DC quantizer to apply to the dequantization matrices.</param>
public void SetDcQuant(InlineArray3<float> dc)
{
for (int c = 0; c < 3; c++)
{
this.dcQuant[c] = 1f / dc[c];
this.inverseDcQuant[c] = dc[c];
}
}
/// <summary>
/// Sets custom quantizer encodings for transform functions.
/// </summary>
/// <param name="encodings">The encodings to identify required transform functions.</param>
public void SetEncodings(JxlQuantizerEncoding[] encodings)
{
this.encodings = encodings;
this.computedMask = 0;
}
/// <summary>
/// Returns quantizer encodings for this dequant matrices instance.
/// </summary>
/// <returns>
/// Encodings set by the <see cref="SetEncodings(JxlQuantizerEncoding[])"/> method.
/// By default (when the aforementioned method wasn't invoked), the result
/// is simply an empty span.
/// </returns>
public Span<JxlQuantizerEncoding> GetEncodings() => this.encodings;
}

25
src/ImageSharp/Formats/Jxl/Processing/JxlEndianness.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
/// <summary>
/// Specifies the ordering of multi-byte data.
/// </summary>
internal enum JxlEndianness : byte
{
/// <summary>
/// Use endianness of the CPU/system.
/// </summary>
Native,
/// <summary>
/// Force little endian.
/// </summary>
Little,
/// <summary>
/// Force big endian.
/// </summary>
Big
}

5
src/ImageSharp/Formats/Jxl/Processing/JxlLoopFilter.cs

@ -21,6 +21,11 @@ internal sealed class JxlLoopFilter : IJxlFields
/// </summary>
private const float InverseSigmaNum = -1.1715728752538099024f;
/// <summary>
/// <see cref="InverseSigmaNum"/> / 3
/// </summary>
public const float MinimumSigma = -3.90524291751269967465540850526868f;
/// <summary>
/// Gets the number of EPF (Edge-preserving filter) sharp entries.
/// </summary>

53
src/ImageSharp/Formats/Jxl/Processing/JxlMath.cs

@ -3,6 +3,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
@ -679,4 +680,56 @@ internal static class JxlMath
return floorLog2 + 1;
}
/// <summary>
/// Computes the hypotenuse of x and y.
/// </summary>
/// <param name="x">X</param>
/// <param name="y">Y</param>
/// <returns>Hypotenuse of x and y</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Hypot(double x, double y)
{
x = Math.Abs(x);
y = Math.Abs(y);
if (x < y)
{
RuntimeUtility.Swap(ref x, ref y);
}
if (x == 0.0)
{
return 0.0;
}
double ratio = y / x;
return x * Math.Sqrt(1 + (ratio * ratio));
}
/// <summary>
/// Computes the hypotenuse of x and y.
/// </summary>
/// <param name="x">X</param>
/// <param name="y">Y</param>
/// <returns>Hypotenuse of x and y</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Hypot(float x, float y)
{
x = MathF.Abs(x);
y = MathF.Abs(y);
if (x < y)
{
RuntimeUtility.Swap(ref x, ref y);
}
if (x == 0.0f)
{
return 0.0f;
}
float ratio = y / x;
return x * MathF.Sqrt(1 + (ratio * ratio));
}
}

2
src/ImageSharp/Formats/Jxl/Processing/JxlPixelFormat.cs

@ -24,7 +24,7 @@ internal struct JxlPixelFormat
/// big-endian or little-endian format. Applies to ushort
/// and float data types.
/// </summary>
public JxlEndianness Endianness { get; set; }
public ByteOrder Endianness { get; set; }
/// <summary>
/// Gets or sets the alignment of scanlines to a multiple of

512
src/ImageSharp/Formats/Jxl/Processing/JxlQuantWeights.cs

@ -12,4 +12,516 @@ internal static class JxlQuantWeights
public const int CeilLog2NumPredefinedTables = 0;
public const int Log2NumQuantModes = 3;
/// <summary>
/// DCT quantizer encoding. (6 distance bands)
/// </summary>
public static readonly JxlQuantizerEncoding Dct = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[3150f, 0f, -0.4f, -0.4f, -0.4f, -2f],
[560f, 0f, -0.3f, -0.3f, -0.3f, -0.3f],
[512f, -2f, -1f, 0f, -1f, -2f]
],
6));
/// <summary>
/// Identity quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Identity = JxlQuantizerEncoding.Identity(
[
[280f, 3160f, 3160f],
[60f, 864f, 864f],
[18f, 200f, 200f],
]);
/// <summary>
/// DCT2X2 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct2x2 = JxlQuantizerEncoding.Dct2(
[
[3840f, 2560f, 1280f, 640f, 480f, 300f],
[960f, 640f, 320f, 180f, 140f, 120f],
[640f, 320f, 128f, 64f, 32f, 16f],
]);
/// <summary>
/// DCT4X4 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct4x4 = JxlQuantizerEncoding.Dct4(
new JxlDctQuantWeightParameters(
[
[2200, 0, 0, 0],
[392, 0, 0, 0],
[112, -0.25f, -0.25f, -0.5f]
],
4),
[
[1, 1],
[1, 1],
[1, 1]
]);
/// <summary>
/// DCT16x16 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct16x16 = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[
8996.8725711814115328f,
-1.3000777393353804f,
-0.49424529824571225f,
-0.439093774457103443f,
-0.6350101832695744f,
-0.90177264050827612f,
-1.6162099239887414f,
],
[
3191.48366296844234752f,
-0.67424582104194355f,
-0.80745813428471001f,
-0.44925837484843441f,
-0.35865440981033403f,
-0.31322389111877305f,
-0.37615025315725483f,
],
[
1157.50408145487200256f,
-2.0531423165804414f,
-1.4f,
-0.50687130033378396f,
-0.42708730624733904f,
-1.4856834539296244f,
-4.9209142884401604f,
]
],
7));
/// <summary>
/// DCT32x32 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct32x32 = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[
15718.40830982518931456f,
-1.025f,
-0.98f,
-0.9012f,
-0.4f,
-0.48819395464f,
-0.421064f,
-0.27f,
],
[
7305.7636810695983104f,
-0.8041958212306401f,
-0.7633036457487539f,
-0.55660379990111464f,
-0.49785304658857626f,
-0.43699592683512467f,
-0.40180866526242109f,
-0.27321683125358037f,
],
[
3803.53173721215041536f,
-3.060733579805728f,
-2.0413270132490346f,
-2.0235650159727417f,
-0.5495389509954993f,
-0.4f,
-0.4f,
-0.3f,
]
],
7));
/// <summary>
/// DCT8x16 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct8x16 = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[
7240.7734393502f,
-0.7f,
-0.7f,
-0.2f,
-0.2f,
-0.2f,
-0.5f,
],
[
1448.15468787004f,
-0.5f,
-0.5f,
-0.5f,
-0.2f,
-0.2f,
-0.2f,
],
[
506.854140754517f,
-1.4f,
-0.2f,
-0.5f,
-0.5f,
-1.5f,
-3.6f,
]
],
7));
/// <summary>
/// DCT8x32 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct8x32 = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[
16283.2494710648897f,
-1.7812845336559429f,
-1.6309059012653515f,
-1.0382179034313539f,
-0.85f,
-0.7f,
-0.9f,
-1.2360638576849587f,
],
[
5089.15750884921511936f,
-0.320049391452786891f,
-0.35362849922161446f,
-0.30340000000000003f,
-0.61f,
-0.5f,
-0.5f,
-0.6f,
],
[
3397.77603275308720128f,
-0.321327362693153371f,
-0.34507619223117997f,
-0.70340000000000003f,
-0.9f,
-1.0f,
-1.0f,
-1.1754605576265209f,
]
],
8));
/// <summary>
/// DCT16x32 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct16x32 = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[
13844.97076442300573f,
-0.97113799999999995f,
-0.658f,
-0.42026f,
-0.22712f,
-0.2206f,
-0.226f,
-0.6f,
],
[
4798.964084220744293f,
-0.61125308982767057f,
-0.83770786552491361f,
-0.79014862079498627f,
-0.2692727459704829f,
-0.38272769465388551f,
-0.22924222653091453f,
-0.20719098826199578f,
],
[
1807.236946760964614f,
-1.2f,
-1.2f,
-0.7f,
-0.7f,
-0.7f,
-0.4f,
-0.5f,
]
],
8));
/// <summary>
/// DCT4x8 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct4x8 = JxlQuantizerEncoding.Dct4x8(
new JxlDctQuantWeightParameters(
[
[
2198.050556016380522f,
-0.96269623020744692f,
-0.76194253026666783f,
-0.6551140670773547f
],
[
764.3655248643528689f,
-0.92630200888366945f,
-0.9675229603596517f,
-0.27845290869168118f
],
[
527.107573587542228f,
-1.4594385811273854f,
-1.450082094097871593f,
-1.5843722511996204f
]
],
4),
[1, 1, 1]);
/// <summary>
/// AFV quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Afv = JxlQuantizerEncoding.Afv(
Dct4x8.DctParameters!,
Dct4x4.DctParameters!,
[
[3072, 3072, 256, 256, 256, 414, 0, 0, 0],
[1024, 1024, 50, 50, 50, 58, 0, 0, 0],
[384, 384, 12, 12, 12, 22, -0.25f, -0.25f, -0.25f]
]);
/// <summary>
/// DCT64x64 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct64x64 = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[
0.9f * 26629.073922049845f,
-1.025f,
-0.78f,
-0.65012f,
-0.19041574084286472f,
-0.20819395464f,
-0.421064f,
-0.32733845535848671f,
],
[
0.9f * 9311.3238710010046f,
-0.3041958212306401f,
-0.3633036457487539f,
-0.35660379990111464f,
-0.3443074455424403f,
-0.33699592683512467f,
-0.30180866526242109f,
-0.27321683125358037f,
],
[
0.9f * 4992.2486445538634f,
-1.2f,
-1.2f,
-0.8f,
-0.7f,
-0.7f,
-0.4f,
-0.5f,
]
],
8));
/// <summary>
/// DCT32x64 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct32x64 = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[
0.65f * 23629.073922049845f,
-1.025f,
-0.78f,
-0.65012f,
-0.19041574084286472f,
-0.20819395464f,
-0.421064f,
-0.32733845535848671f,
],
[
0.65f * 8611.3238710010046f,
-0.3041958212306401f,
-0.3633036457487539f,
-0.35660379990111464f,
-0.3443074455424403f,
-0.33699592683512467f,
-0.30180866526242109f,
-0.27321683125358037f,
],
[
0.65f * 4492.2486445538634f,
-1.2f,
-1.2f,
-0.8f,
-0.7f,
-0.7f,
-0.4f,
-0.5f,
]
],
8));
/// <summary>
/// DCT128x128 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct128x128 = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[
1.8f * 26629.073922049845f,
-1.025f,
-0.78f,
-0.65012f,
-0.19041574084286472f,
-0.20819395464f,
-0.421064f,
-0.32733845535848671f,
],
[
1.8f * 9311.3238710010046f,
-0.3041958212306401f,
-0.3633036457487539f,
-0.35660379990111464f,
-0.3443074455424403f,
-0.33699592683512467f,
-0.30180866526242109f,
-0.27321683125358037f,
],
[
1.8f * 4992.2486445538634f,
-1.2f,
-1.2f,
-0.8f,
-0.7f,
-0.7f,
-0.4f,
-0.5f,
]
],
8));
/// <summary>
/// DCT64x128 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct64x128 = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[
1.3f * 23629.073922049845f,
-1.025f,
-0.78f,
-0.65012f,
-0.19041574084286472f,
-0.20819395464f,
-0.421064f,
-0.32733845535848671f,
],
[
1.3f * 8611.3238710010046f,
-0.3041958212306401f,
-0.3633036457487539f,
-0.35660379990111464f,
-0.3443074455424403f,
-0.33699592683512467f,
-0.30180866526242109f,
-0.27321683125358037f,
],
[
1.3f * 4492.2486445538634f,
-1.2f,
-1.2f,
-0.8f,
-0.7f,
-0.7f,
-0.4f,
-0.5f,
]
],
8));
/// <summary>
/// DCT256x256 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct256x256 = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[
3.6f * 26629.073922049845f,
-1.025f,
-0.78f,
-0.65012f,
-0.19041574084286472f,
-0.20819395464f,
-0.421064f,
-0.32733845535848671f,
],
[
3.6f * 9311.3238710010046f,
-0.3041958212306401f,
-0.3633036457487539f,
-0.35660379990111464f,
-0.3443074455424403f,
-0.33699592683512467f,
-0.30180866526242109f,
-0.27321683125358037f,
],
[
3.6f * 4992.2486445538634f,
-1.2f,
-1.2f,
-0.8f,
-0.7f,
-0.7f,
-0.4f,
-0.5f,
]
],
8));
/// <summary>
/// DCT128x256 quantizer encoding.
/// </summary>
public static readonly JxlQuantizerEncoding Dct128x256 = JxlQuantizerEncoding.Dct(
new JxlDctQuantWeightParameters(
[
[
2.6f * 23629.073922049845f,
-1.025f,
-0.78f,
-0.65012f,
-0.19041574084286472f,
-0.20819395464f,
-0.421064f,
-0.32733845535848671f,
],
[
2.6f * 8611.3238710010046f,
-0.3041958212306401f,
-0.3633036457487539f,
-0.35660379990111464f,
-0.3443074455424403f,
-0.33699592683512467f,
-0.30180866526242109f,
-0.27321683125358037f,
],
[
2.6f * 4492.2486445538634f,
-1.2f,
-1.2f,
-0.8f,
-0.7f,
-0.7f,
-0.4f,
-0.5f,
]
],
8));
}

12
src/ImageSharp/Formats/Jxl/Processing/JxlQuantizer.cs

@ -134,7 +134,7 @@ internal sealed class JxlQuantizer
/// <summary>
/// Gets the default bias for quant.
/// </summary>
private static ReadOnlySpan<float> DefaultQuantBias =>
public static ReadOnlySpan<float> DefaultQuantBias =>
[
1.0f - 0.05465007330715401f,
1.0f - 0.07005449891748593f,
@ -165,7 +165,7 @@ internal sealed class JxlQuantizer
/// </summary>
/// <param name="scale">The new scale</param>
/// <returns>The scale value, scaled by the global scale.</returns>
private float ScaleGlobalScale(float scale)
public float ScaleGlobalScale(float scale)
{
int newGlobalScale = (int)MathF.Round(this.globalScale * scale, MidpointRounding.AwayFromZero);
float scaleOut = newGlobalScale * 1.0f / this.globalScale;
@ -199,7 +199,7 @@ internal sealed class JxlQuantizer
/// <param name="c">The quantization index</param>
/// <returns>The dequant matrix.</returns>
public ReadOnlySpan<float> DequantMatrix(JxlAcStrategyType strategy, int c)
=> this.dequant.Matrix(strategy, c);
=> this.dequant!.GetMatrix(strategy, c);
/// <summary>
/// Returns the inverse dequant matrix.
@ -208,21 +208,21 @@ internal sealed class JxlQuantizer
/// <param name="c">The quantization index</param>
/// <returns>The inverse dequant matrix.</returns>
public ReadOnlySpan<float> InverseDequantMatrix(JxlAcStrategyType strategy, int c)
=> this.dequant.InverseMatrix(strategy, c);
=> this.dequant!.GetInverseMatrix(strategy, c);
/// <summary>
/// Returns the DC quantization step.
/// </summary>
/// <param name="c">The quantization index</param>
/// <returns>The DC quantization step</returns>
public float GetDcStep(int c) => this.InverseQuantDc * this.dequant.DcQuant(c);
public float GetDcStep(int c) => this.InverseQuantDc * this.dequant!.GetDcQuant(c);
/// <summary>
/// Returns the inverse DC quantization step.
/// </summary>
/// <param name="c">The quantization index</param>
/// <returns>The inverse DC quantization step</returns>
public float GetInverseDcStep(int c) => this.dequant.InverseDcQuant(c) * (this.Scale * this.quantDc);
public float GetInverseDcStep(int c) => this.dequant!.GetInverseDcQuant(c) * (this.Scale * this.quantDc);
/// <summary>
/// Creates JXL quantizer parameters with values reflecting those in this quantizer instance.

46
src/ImageSharp/Formats/Jxl/Processing/JxlQuantizerConstants.cs

@ -0,0 +1,46 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
/// <summary>
/// Shared constants used by the quantizer.
/// </summary>
internal static class JxlQuantizerConstants
{
/// <summary>
/// Total number of quantization tables.
/// </summary>
public const byte NumberOfQuantizerTables = (byte)(JxlQuantTable.DCT128X256 + 1);
/// <summary>
/// Gets the inverse DC quantization table.
/// </summary>
public static ReadOnlySpan<float> InverseDcQuant => [4096f, 512f, 256f];
/// <summary>
/// Gets the forward DC quantization table.
/// </summary>
public static ReadOnlySpan<float> DcQuant => [
1f / 4096f,
1f / 512f,
1f / 256f];
/// <summary>
/// Gets a translation table for converting AC strategies to quant tables.
/// Simply pass the index of the AC strategy enum and you'll get back the
/// matching quant table.
/// </summary>
public static ReadOnlySpan<JxlQuantTable> AcStrategyToQuantTableMap =>
[
JxlQuantTable.DCT, JxlQuantTable.IDENTITY, JxlQuantTable.DCT2X2,
JxlQuantTable.DCT4X4, JxlQuantTable.DCT16X16, JxlQuantTable.DCT32X32,
JxlQuantTable.DCT8X16, JxlQuantTable.DCT8X16, JxlQuantTable.DCT8X32,
JxlQuantTable.DCT8X32, JxlQuantTable.DCT16X32, JxlQuantTable.DCT16X32,
JxlQuantTable.DCT4X8, JxlQuantTable.DCT4X8, JxlQuantTable.AFV0,
JxlQuantTable.AFV0, JxlQuantTable.AFV0, JxlQuantTable.AFV0,
JxlQuantTable.DCT64X64, JxlQuantTable.DCT32X64, JxlQuantTable.DCT32X64,
JxlQuantTable.DCT128X128, JxlQuantTable.DCT64X128, JxlQuantTable.DCT64X128,
JxlQuantTable.DCT256X256, JxlQuantTable.DCT128X256, JxlQuantTable.DCT128X256
];
}

22
src/ImageSharp/Formats/Jxl/Processing/JxlQuantizerEncoding.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
/// <summary>
@ -56,27 +54,27 @@ internal sealed class JxlQuantizerEncoding
/// <summary>
/// Gets or sets the weights for the identity transform.
/// </summary>
public InlineArray3<InlineArray3<float>> IdWeights { get; set; }
public float[][]? IdWeights { get; set; }
/// <summary>
/// Gets or sets the weights for the DCT2 transform.
/// </summary>
public InlineArray3<InlineArray6<float>> Dct2Weights { get; set; }
public float[][]? Dct2Weights { get; set; }
/// <summary>
/// Gets or sets the multipliers for the DCT4 transform.
/// </summary>
public InlineArray3<InlineArray6<float>> Dct4Multipliers { get; set; }
public float[][]? Dct4Multipliers { get; set; }
/// <summary>
/// Gets or sets the weights for the AFV transform.
/// </summary>
public InlineArray3<InlineArray9<float>> AfvWeights { get; set; }
public float[][]? AfvWeights { get; set; }
/// <summary>
/// Gets or sets the multipliers for the 4x8 DCT block-based transform.
/// </summary>
public InlineArray3<float> Dct4x8Multipliers { get; set; }
public float[]? Dct4x8Multipliers { get; set; }
/// <summary>
/// Gets or sets the explicit quantization table (like in JPEG).
@ -123,7 +121,7 @@ internal sealed class JxlQuantizerEncoding
/// </summary>
/// <param name="xybWeights">Weights for the identity transform.</param>
/// <returns>A new Identity quantizer encoding.</returns>
public static JxlQuantizerEncoding Identity(in InlineArray3<InlineArray3<float>> xybWeights)
public static JxlQuantizerEncoding Identity(float[][] xybWeights)
=> new()
{
Mode = JxlQuantMode.Id,
@ -136,7 +134,7 @@ internal sealed class JxlQuantizerEncoding
/// </summary>
/// <param name="xybWeights">Weights for the DCT2x2 transform.</param>
/// <returns>A new DCT2x2 quantizer encoding.</returns>
public static JxlQuantizerEncoding Dct2(in InlineArray3<InlineArray6<float>> xybWeights)
public static JxlQuantizerEncoding Dct2(float[][] xybWeights)
=> new()
{
Mode = JxlQuantMode.Dct2,
@ -150,7 +148,7 @@ internal sealed class JxlQuantizerEncoding
/// <param name="parameters">Quantizer weights for the DCT4x4 transform.</param>
/// <param name="xybMul">XYB multipliers for the DCT4x4 transform.</param>
/// <returns>A new DCT4x4 quantizer encoding.</returns>
public static JxlQuantizerEncoding Dct4(JxlDctQuantWeightParameters parameters, in InlineArray3<InlineArray6<float>> xybMul)
public static JxlQuantizerEncoding Dct4(JxlDctQuantWeightParameters parameters, float[][] xybMul)
=> new()
{
Mode = JxlQuantMode.Dct4,
@ -165,7 +163,7 @@ internal sealed class JxlQuantizerEncoding
/// <param name="parameters">Quantizer weights for the DCT4x8 transform.</param>
/// <param name="xybMul">XYB multipliers for the DCT4x8 transform.</param>
/// <returns>A new DCT4x8 quantizer encoding.</returns>
public static JxlQuantizerEncoding Dct4x8(JxlDctQuantWeightParameters parameters, in InlineArray3<float> xybMul)
public static JxlQuantizerEncoding Dct4x8(JxlDctQuantWeightParameters parameters, float[] xybMul)
=> new()
{
Mode = JxlQuantMode.Dct4x8,
@ -197,7 +195,7 @@ internal sealed class JxlQuantizerEncoding
public static JxlQuantizerEncoding Afv(
JxlDctQuantWeightParameters params4x8,
JxlDctQuantWeightParameters params4x4,
in InlineArray3<InlineArray9<float>> weights)
float[][] weights)
=> new()
{
Mode = JxlQuantMode.Afv,

133
src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.StoreInterleaved.Generated.cs

@ -0,0 +1,133 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
internal static partial class JxlSimdUtils
{
public static void StoreInterleaved<T>(Vector<T> v1, Vector<T> v2, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
}
public static void StoreInterleaved<T>(Vector<T> v1, Vector<T> v2, Vector<T> v3, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
}
public static void StoreInterleaved<T>(Vector<T> v1, Vector<T> v2, Vector<T> v3, Vector<T> v4, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
}
public static void StoreInterleaved<T>(Vector<T> v1, Vector<T> v2, Vector<T> v3, Vector<T> v4, Vector<T> v5, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4));
}
public static void StoreInterleaved<T>(Vector<T> v1, Vector<T> v2, Vector<T> v3, Vector<T> v4, Vector<T> v5, Vector<T> v6, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4));
v6.StoreUnsafe(ref Unsafe.Add(ref memory, 5));
}
public static void StoreInterleaved<T>(Vector128<T> v1, Vector128<T> v2, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
}
public static void StoreInterleaved<T>(Vector128<T> v1, Vector128<T> v2, Vector128<T> v3, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
}
public static void StoreInterleaved<T>(Vector128<T> v1, Vector128<T> v2, Vector128<T> v3, Vector128<T> v4, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
}
public static void StoreInterleaved<T>(Vector128<T> v1, Vector128<T> v2, Vector128<T> v3, Vector128<T> v4, Vector128<T> v5, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4));
}
public static void StoreInterleaved<T>(Vector128<T> v1, Vector128<T> v2, Vector128<T> v3, Vector128<T> v4, Vector128<T> v5, Vector128<T> v6, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4));
v6.StoreUnsafe(ref Unsafe.Add(ref memory, 5));
}
public static void StoreInterleaved<T>(Vector256<T> v1, Vector256<T> v2, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
}
public static void StoreInterleaved<T>(Vector256<T> v1, Vector256<T> v2, Vector256<T> v3, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
}
public static void StoreInterleaved<T>(Vector256<T> v1, Vector256<T> v2, Vector256<T> v3, Vector256<T> v4, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
}
public static void StoreInterleaved<T>(Vector256<T> v1, Vector256<T> v2, Vector256<T> v3, Vector256<T> v4, Vector256<T> v5, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4));
}
public static void StoreInterleaved<T>(Vector256<T> v1, Vector256<T> v2, Vector256<T> v3, Vector256<T> v4, Vector256<T> v5, Vector256<T> v6, ref T memory)
{
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4));
v6.StoreUnsafe(ref Unsafe.Add(ref memory, 5));
}
}

44
src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.StoreInterleaved.tt

@ -0,0 +1,44 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".Generated.cs" #>
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
internal static partial class JxlSimdUtils
{
<#
string[] vectorTypes = [
"Vector<T>",
"Vector128<T>",
"Vector256<T>"
];
const int maxVectorSize = 6;
foreach (string vect in vectorTypes) {
for (int i = 2; i <= maxVectorSize; i++) {
List<string> vectorParameters = [];
for (int j = 0; j < i; j++) {
vectorParameters.Add($"{vect} v{j + 1}");
}
string inlineParameters = string.Join(", ", vectorParameters) + ", ";
#>
public static void StoreInterleaved<T>(<#= inlineParameters #>ref T memory)
{
<# for (int j = 0; j < i; j++) { #>
v<#= j + 1 #>.StoreUnsafe(ref Unsafe.Add(ref memory, <#= j #>));
<# } #>
}
<# } } #>
}

104
src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.cs

@ -0,0 +1,104 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
/// <summary>
/// Shared SIMD-accelerated utilities.
/// </summary>
internal static partial class JxlSimdUtils
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<int> ConcatLowerLower(Vector256<int> a, Vector256<int> b) => Vector256.Create(a.GetLower(), b.GetLower());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<int> ConcatUpperUpper(Vector256<int> a, Vector256<int> b) => Vector256.Create(a.GetUpper(), b.GetUpper());
public static void Transpose8x8Block(Span<int> fromSpan, Span<int> toSpan, int stride)
{
ref int from = ref MemoryMarshal.GetReference(fromSpan);
ref int to = ref MemoryMarshal.GetReference(toSpan);
if (Vector256.IsHardwareAccelerated)
{
Vector256<int> i0 = Vector256.LoadUnsafe(ref from);
Vector256<int> i1 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, stride));
Vector256<int> i2 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 2 * stride));
Vector256<int> i3 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 3 * stride));
Vector256<int> i4 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 4 * stride));
Vector256<int> i5 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 5 * stride));
Vector256<int> i6 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 6 * stride));
Vector256<int> i7 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 7 * stride));
Vector256<int> q0 = Vector256_.InterleaveLower(i0, i2);
Vector256<int> q1 = Vector256_.InterleaveLower(i1, i3);
Vector256<int> q2 = Vector256_.InterleaveUpper(i0, i2);
Vector256<int> q3 = Vector256_.InterleaveUpper(i1, i3);
Vector256<int> q4 = Vector256_.InterleaveLower(i4, i6);
Vector256<int> q5 = Vector256_.InterleaveLower(i5, i7);
Vector256<int> q6 = Vector256_.InterleaveUpper(i4, i6);
Vector256<int> q7 = Vector256_.InterleaveUpper(i5, i7);
Vector256<int> r0 = Vector256_.InterleaveLower(q0, q1);
Vector256<int> r1 = Vector256_.InterleaveUpper(q0, q1);
Vector256<int> r2 = Vector256_.InterleaveLower(q2, q3);
Vector256<int> r3 = Vector256_.InterleaveUpper(q2, q3);
Vector256<int> r4 = Vector256_.InterleaveLower(q4, q5);
Vector256<int> r5 = Vector256_.InterleaveUpper(q4, q5);
Vector256<int> r6 = Vector256_.InterleaveLower(q6, q7);
Vector256<int> r7 = Vector256_.InterleaveUpper(q6, q7);
i0 = ConcatLowerLower(r4, r0);
i1 = ConcatLowerLower(r5, r1);
i2 = ConcatLowerLower(r6, r2);
i3 = ConcatLowerLower(r7, r3);
i4 = ConcatUpperUpper(r4, r0);
i5 = ConcatUpperUpper(r5, r1);
i6 = ConcatUpperUpper(r6, r2);
i7 = ConcatUpperUpper(r7, r3);
i0.StoreUnsafe(ref to);
i1.StoreUnsafe(ref Unsafe.Add(ref to, 8));
i2.StoreUnsafe(ref Unsafe.Add(ref to, 16));
i3.StoreUnsafe(ref Unsafe.Add(ref to, 24));
i4.StoreUnsafe(ref Unsafe.Add(ref to, 32));
i5.StoreUnsafe(ref Unsafe.Add(ref to, 40));
i6.StoreUnsafe(ref Unsafe.Add(ref to, 48));
i7.StoreUnsafe(ref Unsafe.Add(ref to, 56));
}
else
{
// Vector128 fallback
for (int n = 0; n < 8; n += 4)
{
for (int m = 0; m < 8; m += 4)
{
Vector128<int> p0 = Vector128.LoadUnsafe(ref Unsafe.Add(ref from, (n * stride) + m));
Vector128<int> p1 = Vector128.LoadUnsafe(ref Unsafe.Add(ref from, ((n + 1) * stride) + m));
Vector128<int> p2 = Vector128.LoadUnsafe(ref Unsafe.Add(ref from, ((n + 2) * stride) + m));
Vector128<int> p3 = Vector128.LoadUnsafe(ref Unsafe.Add(ref from, ((n + 3) * stride) + m));
Vector128<int> q0 = Vector128_.InterleaveLower(p0, p2);
Vector128<int> q1 = Vector128_.InterleaveLower(p1, p3);
Vector128<int> q2 = Vector128_.InterleaveUpper(p0, p2);
Vector128<int> q3 = Vector128_.InterleaveUpper(p1, p3);
Vector128<int> r0 = Vector128_.InterleaveLower(q0, q1);
Vector128<int> r1 = Vector128_.InterleaveUpper(q0, q1);
Vector128<int> r2 = Vector128_.InterleaveLower(q2, q3);
Vector128<int> r3 = Vector128_.InterleaveUpper(q2, q3);
r0.StoreUnsafe(ref Unsafe.Add(ref to, (m * 8) + n));
r1.StoreUnsafe(ref Unsafe.Add(ref to, ((m + 1) * 8) + n));
r2.StoreUnsafe(ref Unsafe.Add(ref to, ((m + 2) * 8) + n));
r3.StoreUnsafe(ref Unsafe.Add(ref to, ((m + 3) * 8) + n));
}
}
}
}
}

2
src/ImageSharp/Formats/Jxl/Processing/JxlWeightsSeparable5.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
internal struct JxlWeightsSeparable5

390
src/ImageSharp/Formats/Jxl/Processing/Modular/Encoding/ContextPrediction/JxlContextPrediction.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.ContextPrediction;
@ -10,6 +11,10 @@ namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.ContextPr
/// </summary>
internal static class JxlContextPrediction
{
private const int ExtraPropertiesPerChannel = 4;
private const int NumberOfProperties = 1;
public static void SetPredictorMode(int i, JxlModularHeader header)
{
ref uint wr = ref header.GetWReference();
@ -94,4 +99,389 @@ internal static class JxlContextPrediction
break;
}
}
/// <summary>
/// Returns true if the (meta)predictor makes use of the weighted predictor.
/// </summary>
/// <param name="predictor">The input predictor.</param>
/// <returns>Value indicating whether the predictor uses weighted prediction.</returns>
public static bool IsWeightedPredictor(JxlPredictor predictor) => predictor switch
{
JxlPredictor.Zero or
JxlPredictor.Left or
JxlPredictor.Top or
JxlPredictor.Average0 or
JxlPredictor.Select or
JxlPredictor.Gradient => false,
JxlPredictor.Weighted => true,
JxlPredictor.TopRight or
JxlPredictor.TopLeft or
JxlPredictor.LeftLeft or
JxlPredictor.Average1 or
JxlPredictor.Average2 or
JxlPredictor.Average3 or
JxlPredictor.Average4 => false,
JxlPredictor.Best or
JxlPredictor.Variable => true,
_ => false,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ClampedGradient(int n, int w, int l)
{
int min = Math.Min(n, w);
int max = Math.Max(n, w);
int gradient = n + w - l;
int clamp = l < min ? max : gradient;
return l > max ? min : clamp;
}
// This is actually a simple Paeth predictor, we'd often see
// this in PNG files
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Select(int a, int b, int c)
{
int p = a + b - c;
int pa = Numerics.Abs(p - a);
int pb = Numerics.Abs(p - b);
return pa < pb ? a : b;
}
public static void PrecomputeReferences(JxlModularChannel channel, int y, JxlModularImage image, int i, JxlModularChannel references)
{
references.Plane.Clear();
int offset = 0;
int numExtraProps = references.Width;
int oneRow = references.Plane.PixelsPerRow;
JxlModularChannel channelI = image.Channels[i];
for (int j = i - 1; i >= 0 && offset < numExtraProps; j--)
{
JxlModularChannel channelJ = image.Channels[j];
if (channelJ.Width != channelI.Width || channelJ.Height != channelI.Height)
{
continue;
}
if (channelJ.HorizontalShift != channelI.HorizontalShift ||
channelJ.VerticalShift != channelI.VerticalShift)
{
continue;
}
Span<int> rp = references.GetRow(0)[offset..];
Span<int> rpp = channelJ.GetRow(y);
Span<int> rpprev = channelJ.GetRow(y > 0 ? y - 1 : 0);
for (int x = 0; x < channel.Width; x++, rp = rp[oneRow..])
{
int v = rpp[x];
rp[0] = Numerics.Abs(v);
rp[1] = v;
// Neighboring variables
int vleft = x > 0 ? rpp[x - 1] : 0;
int vtop = y > 0 ? rpprev[x] : vleft;
int vtopleft = x > 0 && y > 0 ? rpprev[x - 1] : vleft;
// Prediction
int vpredicted = ClampedGradient(vleft, vtop, vtopleft);
rp[2] = Numerics.Abs(v - vpredicted);
rp[3] = v - vpredicted;
}
offset += ExtraPropertiesPerChannel;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InitializePropertiesForRow(Span<int> p, InlineArray2<int> staticProperties, int y)
{
p[0] = staticProperties[0];
p[1] = staticProperties[1];
p[2] = y;
p[9] = 0; // Local gradient
}
// Prediction for one pixel using neighbors
[MethodImpl(InliningOptions.HotPath)] // This method is called frequently
public static int PredictOne(
JxlPredictor p,
int left,
int top,
int toptop,
int topleft,
int topright,
int leftleft,
int toprightright,
int wpPred) => p switch
{
JxlPredictor.Zero => 0,
JxlPredictor.Left => left,
JxlPredictor.Top => top,
JxlPredictor.Select => Select(left, top, topleft),
JxlPredictor.Weighted => wpPred,
JxlPredictor.Gradient => ClampedGradient(left, top, topleft),
JxlPredictor.TopLeft => topleft,
JxlPredictor.TopRight => topright,
JxlPredictor.LeftLeft => leftleft,
JxlPredictor.Average0 => (left + top) / 2,
JxlPredictor.Average1 => (left + topleft) / 2,
JxlPredictor.Average2 => (topleft + top) / 2,
JxlPredictor.Average3 => (top + topright) / 2,
JxlPredictor.Average4 => ((6 * top) - (2 * toptop) + (7 * left) + (1 * leftleft) +
(1 * toprightright) + (3 * topright) + 8) /
16,
_ => 0,
};
public static JxlPredictionResult Predict(
JxlPredictorMode mode,
Span<int> p, // contains properties
int w, // block width
ref int pp, // This is a reference to the output pixel stored in row-major order. Negative offsets are accessed to reference other pixels in the image, specifically neighboring pixles.
int oneRow, // Number of pixels on one row
int x,
int y,
JxlPredictor predictor,
JxlMaTreeLookup? lookup,
JxlModularChannel? references,
JxlModularState? wpState,
Span<int> predictions)
{
int offset = 3; // Start at position 3 because of 2 static properties + y
// Status flags
// computeProperties = should the p (properties) variable be updated?
// nec = are there no edge cases?
bool computeProperties = (mode & JxlPredictorMode.UseTree) != 0 || (mode & JxlPredictorMode.ForceComputeProperties) != 0;
bool nec = (mode & JxlPredictorMode.NoEdgeCases) != 0;
// The following variables are neighboring pixels relative to the pixel to predict.
// Pixels may be unavailable and therefore replaced with default values. For example,
// at Y=0, the top pixel may not be available because we're already at the very top
// of the image, there's no "above" of that.
int left = nec || x > 0 ? Unsafe.Subtract(ref pp, 1) : (y > 0 ? Unsafe.Subtract(ref pp, oneRow) : 0); // ⬅️ (or 0 if unavailable)
int top = nec || y > 0 ? Unsafe.Subtract(ref pp, oneRow) : left; // ⬆️ (or ⬅️ if unavailable)
int topleft = nec || (x > 0 && y > 0) ? Unsafe.Add(ref pp, -1 - oneRow) : left; // ↗️ (or ⬅️ if unavailable)
int topright = nec || (x + 1 < w && y > 0) ? Unsafe.Add(ref pp, 1 - oneRow) : top; // ↖️ (or ⬆️ if unavailable)
int leftleft = nec || x > 1 ? Unsafe.Subtract(ref pp, 2) : left; // ⬅️⬅️ (or ⬅️ if unavailable)
int toptop = nec || y > 1 ? Unsafe.Add(ref pp, -oneRow - oneRow) : top; // ⬆️⬆️ (or ⬆️ if unavailable)
int toprightright = nec || (x + 2 < w && y > 0) ? Unsafe.Add(ref pp, 2 - oneRow) : topright; // ↗️➡️ (or ↗️ if unavailable)
if (computeProperties)
{
p[offset++] = x;
p[offset++] = top > 0 ? top : -top;
p[offset++] = left > 0 ? left : -left;
p[offset++] = top;
p[offset++] = left;
// Local gradient
p[offset] = left - p[offset + 1];
offset++;
// Local gradient
p[offset++] = left + top - topleft;
// FFV1 context properties
p[offset++] = left - topleft;
p[offset++] = topleft - top;
p[offset++] = top - topright;
p[offset++] = top - toptop;
p[offset++] = left - leftleft;
}
// Predicted weighted prediction value
int wpPred = 0;
if ((mode & JxlPredictorMode.UseWeightedPrediction) != 0)
{
if (wpState is null)
{
throw new InvalidOperationException("Weighted prediction state is missing");
}
wpPred = unchecked((int)wpState.Predict(computeProperties, x, y, w, top, left, topright, topleft, toptop, p, offset));
}
if (!nec && computeProperties)
{
if (references is null)
{
throw new InvalidOperationException("References are missing");
}
offset += NumberOfProperties;
// Extra properties
Span<int> rp = references.GetRow(x);
for (int i = 0; i < references.Width; i++)
{
p[offset++] = rp[i];
}
}
JxlPredictionResult predResult = default;
if ((mode & JxlPredictorMode.UseTree) != 0)
{
if (lookup is null)
{
throw new InvalidOperationException("Lookup is missing");
}
JxlMaTreeLookupResult result = lookup.Lookup(p);
predictor = result.Predictor;
predResult = new((int)result.Context, result.Offset, default, result.Multiplier);
}
if ((mode & JxlPredictorMode.AllPredictions) != 0)
{
for (int i = 0; i < JxlPredictorFacts.ModularPredictors; i++)
{
predictions[i] = PredictOne((JxlPredictor)i, left, top, toptop, topleft, topright, leftleft, toprightright, wpPred);
}
}
predResult = new(
predResult.Context,
predResult.Guess + PredictOne(predictor, left, top, toptop, topleft, topright, leftleft, toprightright, wpPred),
predictor,
predResult.Multiplier);
return predResult;
}
// The following methods are just wrappers over the Predict
// method.
// See https://github.com/libjxl/libjxl/blob/main/lib/jxl/modular/encoding/context_predict.h#L593-L709
public static JxlPredictionResult PredictNoTreeNoWeightedPrediction(
int w,
ref int pp,
int oneRow,
int x,
int y,
JxlPredictor predictor)
=> Predict(0, [], w, ref pp, oneRow, x, y, predictor, null, null, null, []);
public static JxlPredictionResult PredictNoTreeWeightedPrediction(
int w,
ref int pp,
int oneRow,
int x,
int y,
JxlPredictor predictor,
JxlModularState wpState)
=> Predict(JxlPredictorMode.UseTree, [], w, ref pp, oneRow, x, y, predictor, null, null, wpState, []);
public static JxlPredictionResult PredictTreeNoWeightedPrediction(
Span<int> p,
int w,
ref int pp,
int oneRow,
int x,
int y,
JxlMaTreeLookup treeLookup,
JxlModularChannel references)
=> Predict(JxlPredictorMode.UseTree, p, w, ref pp, oneRow, x, y, JxlPredictor.Zero, treeLookup, references, null, []);
public static JxlPredictionResult PredictTreeNoWeightedPredictionNoEdgeCases(
Span<int> p,
int w,
ref int pp,
int oneRow,
int x,
int y,
JxlMaTreeLookup treeLookup,
JxlModularChannel references)
=> Predict(JxlPredictorMode.UseTree | JxlPredictorMode.NoEdgeCases, p, w, ref pp, oneRow, x, y, JxlPredictor.Zero, treeLookup, references, null, []);
public static JxlPredictionResult PredictTreeWeightedPrediction(
Span<int> p,
int w,
ref int pp,
int oneRow,
int x,
int y,
JxlMaTreeLookup treeLookup,
JxlModularChannel references,
JxlModularState wpState)
=> Predict(JxlPredictorMode.UseTree | JxlPredictorMode.UseWeightedPrediction, p, w, ref pp, oneRow, x, y, JxlPredictor.Zero, treeLookup, references, wpState, []);
public static JxlPredictionResult PredictTreeWeightedPredictionNoEdgeCases(
Span<int> p,
int w,
ref int pp,
int oneRow,
int x,
int y,
JxlMaTreeLookup treeLookup,
JxlModularChannel references,
JxlModularState wpState)
=> Predict(JxlPredictorMode.UseTree | JxlPredictorMode.UseWeightedPrediction | JxlPredictorMode.NoEdgeCases, p, w, ref pp, oneRow, x, y, JxlPredictor.Zero, treeLookup, references, wpState, []);
public static JxlPredictionResult PredictLearn(
Span<int> p,
int w,
ref int pp,
int oneRow,
int x,
int y,
JxlPredictor predictor,
JxlModularChannel references,
JxlModularState wpState)
=> Predict(JxlPredictorMode.ForceComputeProperties | JxlPredictorMode.UseWeightedPrediction, p, w, ref pp, oneRow, x, y, predictor, null, references, wpState, []);
public static JxlPredictionResult PredictLearnAll(
Span<int> p,
int w,
ref int pp,
int oneRow,
int x,
int y,
JxlModularChannel references,
JxlModularState wpState,
Span<int> predictions)
=> Predict(JxlPredictorMode.ForceComputeProperties | JxlPredictorMode.UseWeightedPrediction | JxlPredictorMode.AllPredictions, p, w, ref pp, oneRow, x, y, JxlPredictor.Zero, null, references, wpState, predictions);
public static JxlPredictionResult PredictLearnNoEdgeCases(
Span<int> p,
int w,
ref int pp,
int oneRow,
int x,
int y,
JxlPredictor predictor,
JxlModularChannel references,
JxlModularState wpState)
=> Predict(JxlPredictorMode.ForceComputeProperties | JxlPredictorMode.UseWeightedPrediction | JxlPredictorMode.NoEdgeCases, p, w, ref pp, oneRow, x, y, predictor, null, references, wpState, []);
public static JxlPredictionResult PredictLearnAllNoEdgeCases(
Span<int> p,
int w,
ref int pp,
int oneRow,
int x,
int y,
JxlModularChannel references,
JxlModularState wpState,
Span<int> predictions)
=> Predict(JxlPredictorMode.ForceComputeProperties | JxlPredictorMode.UseWeightedPrediction | JxlPredictorMode.AllPredictions | JxlPredictorMode.NoEdgeCases, p, w, ref pp, oneRow, x, y, JxlPredictor.Zero, null, references, wpState, predictions);
public static JxlPredictionResult PredictAllNoWeightedPrediction(
int w,
ref int pp,
int oneRow,
int x,
int y,
Span<int> predictions)
=> Predict(JxlPredictorMode.AllPredictions, [], w, ref pp, oneRow, x, y, JxlPredictor.Zero, null, null, null, predictions);
}

13
src/ImageSharp/Formats/Jxl/Processing/Modular/Encoding/ContextPrediction/JxlPredictionResult.cs

@ -0,0 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.ContextPrediction;
/// <summary>
/// The result of context prediction.
/// </summary>
/// <param name="Context">Context used in MA lookup.</param>
/// <param name="Guess">Predicted coefficient.</param>
/// <param name="Predictor">Kind of predictor mode used.</param>
/// <param name="Multiplier">Multiplier used in MA lookup.</param>
internal record struct JxlPredictionResult(int Context, int Guess, JxlPredictor Predictor, int Multiplier);

35
src/ImageSharp/Formats/Jxl/Processing/Modular/Encoding/ContextPrediction/JxlPredictorMode.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.ContextPrediction;
/// <summary>
/// Flags for context prediction.
/// </summary>
[Flags]
internal enum JxlPredictorMode : byte
{
/// <summary>
/// Should tree-based prediction be used?
/// </summary>
UseTree = 1,
/// <summary>
/// Should the weighted predictor be used?
/// </summary>
UseWeightedPrediction = 2,
/// <summary>
/// Should properties be computed? (When this bit is 0,
/// the properties are not set and therefore have their
/// default values)
/// </summary>
ForceComputeProperties = 4,
/// <summary>
/// Try all predictors?
/// </summary>
AllPredictions = 8,
NoEdgeCases = 16
}

24
src/ImageSharp/Formats/Jxl/Processing/Modular/JxlModularChannel.cs

@ -1,27 +1,22 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jxl.Memory;
using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular;
/// <summary>
/// A wrapper over <see cref="JxlPlane{T}"/> for modular operations.
/// A wrapper over <see cref="JxlImageI"/> for modular operations.
/// </summary>
internal sealed class JxlModularChannel
{
/// <summary>
/// Underlying plane buffer.
/// </summary>
private JxlPlane<int> plane;
public JxlModularChannel(Configuration configuration, int width, int height, int horizShift, int vertShift)
{
this.HorizontalShift = horizShift;
this.VerticalShift = vertShift;
this.Width = width;
this.Height = height;
this.plane = JxlPlane<int>.Create(configuration, width, height);
this.Plane = new JxlImageI(configuration, width, height);
}
/// <summary>
@ -55,15 +50,20 @@ internal sealed class JxlModularChannel
/// </summary>
public int Component { get; set; } = -1;
/// <summary>
/// Gets or sets the backing plane buffer.
/// </summary>
public JxlImageI Plane { get; set; }
public void Shrink(Configuration configuration)
{
if (this.plane.XSize == this.Width && this.plane.YSize == this.Height)
if (this.Plane.XSize == this.Width && this.Plane.YSize == this.Height)
{
return;
}
this.plane.Dispose();
this.plane = JxlPlane<int>.Create(configuration, this.Width, this.Height);
this.Plane.Dispose();
this.Plane = new JxlImageI(configuration, this.Width, this.Height);
}
public void Shrink(Configuration configuration, int newWidth, int newHeight)
@ -73,5 +73,5 @@ internal sealed class JxlModularChannel
this.Shrink(configuration);
}
public Span<int> GetRow(int y) => this.plane.GetRow(y);
public Span<int> GetRow(int y) => this.Plane.GetRow(y);
}

10
src/ImageSharp/Formats/Jxl/Processing/Modular/JxlModularImage.cs

@ -7,5 +7,13 @@ internal sealed class JxlModularImage
{
public List<JxlModularChannel> Channels { get; set; } = [];
/// <summary>
/// Gets or sets the total number of metachannels in this image.
/// </summary>
public int MetaChannels { get; set; }
/// <summary>
/// Gets or sets the bit depth used in this image.
/// </summary>
public int BitDepth { get; set; }
}

1227
src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlPalette.cs

File diff suppressed because it is too large

1
src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlRct.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Transforms;

793
src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlSqueeze.cs

@ -0,0 +1,793 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
#pragma warning disable IDE0057 // Use range operator
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Transforms;
/// <summary>
/// Implements the <em>squeeze transform</em>.
/// </summary>
/// <remarks>
/// The squeeze transform in JXL is a reversible
/// wavelet-like decomposition used in the modular mode
/// to reduce redundancy and improve compression,
/// especially for structured or synthetic images.
/// It works by hierarchically splitting channesl
/// into lower-resolution representations plus
/// residuals, giving us multi-resolution coding while
/// remaining lossless.
/// </remarks>
internal static class JxlSqueeze
{
private const int MaxFirstPreviewSize = 8;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int SmoothTendency(int b, int a, int n)
{
int diff = 0;
if (b >= a && a >= n)
{
diff = ((4 * b) - (3 * n) - a + 6) / 12;
if (diff - (diff & 1) > 2 * (b - a))
{
diff = (2 * (b - a)) + 1;
}
if (diff + (diff & 1) > 2 * (a - n))
{
diff = 2 * (a - n);
}
}
else if (b <= a && a <= n)
{
diff = ((4 * b) - (3 * n) - a - 6) / 12;
if (diff + (diff & 1) < 2 * (b - a))
{
diff = (2 * (b - a)) - 1;
}
if (diff - (diff & 1) < 2 * (a - n))
{
diff = 2 * (a - n);
}
}
return diff;
}
// The function operates on 256-bit fixed size vectors,
// 8 elements at a time. It should still work even on CPUs
// without 256-bit vector support (the JIT will translate
// these into 128-bit halves, or scalar without SIMD support).
//
// The FastUnsqueeze method CAN operate on vectors below
// 256-bit, but not above. It's better to simply use Vector256
// rather than duplicate everything. Vector<T> may be a problem
// as its number of elements can be greater than 8 which is too
// much for this method.
[MethodImpl(InliningOptions.HotPath)] // Called on an entire image
private static void FastUnsqueeze(Span<int> pResidual, Span<int> pAvg, Span<int> pNAvg, Span<int> pPout, Span<int> pOut, Span<int> pNOut)
{
Vector256<int> oneThird = Vector256.Create(0x55555556);
ref int pAvgRef = ref MemoryMarshal.GetReference(pAvg);
ref int pNAvgRef = ref MemoryMarshal.GetReference(pNAvg);
ref int pPoutReference = ref MemoryMarshal.GetReference(pPout);
ref int pResidualRef = ref MemoryMarshal.GetReference(pResidual);
ref int pOutRef = ref MemoryMarshal.GetReference(pOut);
ref int pNOutRef = ref MemoryMarshal.GetReference(pNOut);
Vector256<int> avg = Vector256.LoadUnsafe(ref pAvgRef);
Vector256<int> nextAvg = Vector256.LoadUnsafe(ref pNAvgRef);
Vector256<int> top = Vector256.LoadUnsafe(ref pPoutReference);
Vector256<int> ba = top - avg;
Vector256<int> an = avg - nextAvg;
Vector256<int> nonmono = ba ^ an;
Vector256<int> absba = Vector256.Abs(ba);
Vector256<int> absan = Vector256.Abs(an);
Vector256<int> absbn = Vector256.Abs(top - nextAvg);
Vector256<long> a3eh = Vector256_.MultiplyEven(absba, oneThird);
Vector256<long> a3oh = Vector256_.MultiplyOdd(absba, oneThird);
Vector256<int> a3 = BitConverter.IsLittleEndian
? Vector256_.InterleaveOdd(a3eh.AsInt32(), a3oh.AsInt32())
: Vector256_.InterleaveEven(a3eh.AsInt32(), a3oh.AsInt32());
a3 += absbn + Vector256.Create(2);
Vector256<int> absdiff = a3 >> 2;
Vector256<int> skipdiff = Vector256_.NotEqual(ba, Vector256<int>.Zero);
skipdiff &= Vector256_.NotEqual(an, Vector256<int>.Zero);
skipdiff &= Vector256.LessThan(nonmono, Vector256<int>.Zero);
Vector256<int> absBa2 = (absba << 1) + (absdiff & Vector256<int>.One);
absdiff = Vector256.ConditionalSelect(
Vector256.GreaterThan(absdiff, absBa2),
(absba << 1) + Vector256<int>.One,
absdiff);
Vector256<int> absan2 = absan << 1;
absdiff = Vector256.ConditionalSelect(
Vector256.GreaterThan(absdiff + (absdiff & Vector256<int>.One), absan2),
absan2,
absdiff);
Vector256<int> diff1 = Vector256.ConditionalSelect(
Vector256.LessThan(top, nextAvg),
-absdiff,
absdiff);
Vector256<int> tendency = diff1 & ~skipdiff;
Vector256<int> diffMinusTendency = Vector256.LoadUnsafe(ref pResidualRef);
Vector256<int> diff = diffMinusTendency + tendency;
Vector256<int> output = avg + (diff + (diff << 31));
output.StoreUnsafe(ref pOutRef);
(output - diff).StoreUnsafe(ref pNOutRef);
}
public static void InverseHorizontalSqueeze(Configuration configuration, JxlModularImage input, int c, int rc)
{
// Channel offsets should not overflow.
DebugGuard.MustBeLessThan(c, input.Channels.Count, nameof(c));
DebugGuard.MustBeLessThan(rc, input.Channels.Count, nameof(c));
JxlModularChannel inputChannel = input.Channels[c];
JxlModularChannel inputResidualChannel = input.Channels[rc];
if (inputChannel.Width != JxlMath.DivCeil(inputChannel.Width + inputResidualChannel.Width, 2))
{
throw new InvalidOperationException("Invalid width");
}
if (inputChannel.Height != inputResidualChannel.Height)
{
throw new InvalidOperationException("Height of the input channel must be equal to the height of the residual channel");
}
if (inputResidualChannel.Width == 0)
{
input.Channels[c].HorizontalShift--;
return;
}
// Do not dispose.
JxlModularChannel outputChannel = new(
configuration,
inputChannel.Width + inputResidualChannel.Width,
inputChannel.Height,
inputChannel.HorizontalShift - 1,
inputChannel.VerticalShift);
if (inputResidualChannel.Height == 0)
{
input.Channels[c] = outputChannel;
return;
}
// The number of rows a single parallel iteration computes
// is stored here.
const int rowsPerThread = 8;
// rowsPerThread * 9, aligned to the power of 2.
const int rowsPerThreadMul9Alignment = 128;
// rowsPerThread * 8, aligned to the power of 2.
const int rowsPerThreadMul8Alignment = 64;
_ = Parallel.For(0, JxlMath.DivCeil(inputChannel.Height, rowsPerThread), configuration.GetParallelOptions(), idx =>
{
int y0 = idx * rowsPerThread;
int rows = Math.Min(rowsPerThread, inputChannel.Height - y0);
int x = 0;
int onerow_in = inputChannel.Plane.PixelsPerRow;
int onerow_inr = inputResidualChannel.Plane.PixelsPerRow;
int onerow_out = outputChannel.Plane.PixelsPerRow;
Span<int> pResidual = inputResidualChannel.GetRow(y0);
Span<int> pAverage = inputChannel.GetRow(y0);
Span<int> pOut = outputChannel.GetRow(y0);
ref int pOutRef = ref MemoryMarshal.GetReference(pOut);
Span<int> bpAvg = stackalloc int[rowsPerThreadMul9Alignment].Slice(0, rowsPerThread * 9);
Span<int> bpResidual = stackalloc int[rowsPerThreadMul8Alignment].Slice(0, rowsPerThread * 8);
Span<int> bpOutEven = stackalloc int[rowsPerThreadMul8Alignment].Slice(0, rowsPerThread * 8);
Span<int> bpOutOdd = stackalloc int[rowsPerThreadMul8Alignment].Slice(0, rowsPerThread * 8);
Span<int> bpOutEvenT = stackalloc int[rowsPerThreadMul8Alignment].Slice(0, rowsPerThread * 8);
Span<int> bpOutOddT = stackalloc int[rowsPerThreadMul8Alignment].Slice(0, rowsPerThread * 8);
ref int bpOutEvenTRef = ref MemoryMarshal.GetReference(bpOutEvenT);
ref int bpOutOddTRef = ref MemoryMarshal.GetReference(bpOutOddT);
int n = Vector256<int>.Count;
if (inputResidualChannel.Width > 16 && rows == rowsPerThread)
{
for (; x < inputResidualChannel.Width - 9; x += 8)
{
JxlSimdUtils.Transpose8x8Block(pResidual[x..], bpResidual, onerow_inr);
JxlSimdUtils.Transpose8x8Block(pAverage[x..], bpAvg, onerow_in);
for (int y = 0; y < rowsPerThread; y++)
{
bpAvg[64 + y] = pAverage[x + 8 + (onerow_in * y)];
}
for (int i = 0; i < 8; i++)
{
// i * 8
int i8 = i << 3;
FastUnsqueeze(
bpResidual[i8..],
bpAvg[i8..],
bpAvg[(8 * (i + 1))..],
(x + i > 0) ? bpOutOdd[(8 * ((x + i - 1) & 7))..] : bpAvg[i8..],
bpOutEven[i8..],
bpOutOdd[i8..]);
}
JxlSimdUtils.Transpose8x8Block(bpOutEven, bpOutEvenT, 8);
JxlSimdUtils.Transpose8x8Block(bpOutOdd, bpOutOddT, 8);
for (int y = 0; y < rowsPerThread; y++)
{
// y * 8
int y8 = y << 3;
for (int i = 0; i < rowsPerThread; i += n)
{
int offset = y8 + i;
Vector256<int> even = Vector256.LoadUnsafe(ref Unsafe.Add(ref bpOutEvenTRef, offset));
Vector256<int> odd = Vector256.LoadUnsafe(ref Unsafe.Add(ref bpOutOddTRef, offset));
JxlSimdUtils.StoreInterleaved(
even,
odd,
ref Unsafe.Add(ref pOutRef, ((x + i) << 1) + (onerow_out * y)));
}
}
}
}
for (int y = 0; y < rows; y++)
{
UnsqueezeRow(y0 + y, x);
}
});
input.Channels[c] = outputChannel;
void UnsqueezeRow(int y, int x0)
{
Span<int> residual = inputResidualChannel.GetRow(y);
Span<int> average = inputChannel.GetRow(y);
Span<int> output = outputChannel.GetRow(y);
int inputChannelWidth = inputChannel.Width;
int outputChannelWidth = outputChannel.Width;
for (int x = x0; x < inputResidualChannel.Width; x++)
{
int xLsh1 = x << 1; // Prevents left shifting three times. Saves on CPU cycles.
int diffMinusTendency = residual[x];
int avg = average[x];
int nextAverage = x + 1 < inputChannelWidth ? average[x + 1] : avg;
int left = x > 0 ? output[xLsh1 - 1] : avg;
int tendency = SmoothTendency(left, avg, nextAverage);
int diff = diffMinusTendency + tendency;
int a = avg + (diff / 2);
output[xLsh1] = a;
int b = a - diff;
output[xLsh1 + 1] = b;
}
if ((outputChannelWidth & 1) > 0)
{
output[outputChannelWidth - 1] = average[inputChannelWidth - 1];
}
}
}
public static void InverseVerticalSqueeze(Configuration configuration, JxlModularImage input, int c, int rc)
{
// Channel offsets should not overflow.
DebugGuard.MustBeLessThan(c, input.Channels.Count, nameof(c));
DebugGuard.MustBeLessThan(rc, input.Channels.Count, nameof(c));
JxlModularChannel inputChannel = input.Channels[c];
JxlModularChannel inputResidualChannel = input.Channels[rc];
if (inputChannel.Height != JxlMath.DivCeil(inputChannel.Height + inputResidualChannel.Height, 2))
{
throw new InvalidOperationException("Invalid height");
}
if (inputChannel.Width != inputResidualChannel.Width)
{
throw new InvalidOperationException("Width of the input channel must be equal to the width of the residual channel");
}
if (inputResidualChannel.Height == 0)
{
input.Channels[c].VerticalShift--;
return;
}
// Do not dispose.
JxlModularChannel outputChannel = new(
configuration,
inputChannel.Width,
inputChannel.Height + inputResidualChannel.Height,
inputChannel.HorizontalShift,
inputChannel.VerticalShift - 1);
if (inputResidualChannel.Width == 0)
{
input.Channels[c] = outputChannel;
return;
}
// The number of columns a single parallel iteration computes
// is stored here.
const int colsPerThread = 8;
_ = Parallel.For(0, JxlMath.DivCeil(inputChannel.Width, colsPerThread), configuration.GetParallelOptions(), idx =>
{
int x0 = idx * colsPerThread;
int x1 = Math.Min((idx + 1) * colsPerThread, inputChannel.Width);
int w = x1 - x0;
for (int y = 0; y < inputResidualChannel.Height; y++)
{
int yLsh1 = y << 1;
Span<int> pResidual = inputResidualChannel.GetRow(y)[x0..];
Span<int> pAverage = inputChannel.GetRow(y)[x0..];
Span<int> pNAvg = inputChannel.GetRow(y + 1 < inputChannel.Height ? y + 1 : y)[x0..];
Span<int> pOut = outputChannel.GetRow(yLsh1)[x0..];
Span<int> pNOut = outputChannel.GetRow(yLsh1 + 1)[x0..];
Span<int> pPOut = y > 0 ? outputChannel.GetRow(yLsh1 - 1)[x0..] : pNAvg;
int x = 0;
for (; x + 7 < w; x += 8)
{
FastUnsqueeze(
pResidual[x..],
pAverage[x..],
pNAvg[x..],
pPOut[x..],
pOut[x..],
pNOut[x..]);
}
// Remainder
for (; x < w; x++)
{
int avg = pNAvg[x];
int nextAvg = pNAvg[x];
int top = pPOut[x];
int tendency = SmoothTendency(top, avg, nextAvg);
int diffMinusTendency = pResidual[x];
int diff = diffMinusTendency + tendency;
int output = avg + (diff >> 1);
pOut[x] = output;
pNOut[x] = output - diff;
}
}
});
if ((outputChannel.Height & 1) > 0)
{
int y = inputChannel.Height - 1;
Span<int> pAverage = inputChannel.GetRow(y);
Span<int> pOutput = outputChannel.GetRow(y << 1);
for (int x = 0; x < inputChannel.Width; x++)
{
pOutput[x] = pAverage[x];
}
}
input.Channels[c] = outputChannel;
}
public static void InverseSqueeze(Configuration configuration, JxlModularImage input, Span<JxlSqueezeParameters> parameters)
{
int totalNumberOfChannels = input.Channels.Count;
for (int i = parameters.Length - 1; i >= 0; i--)
{
ref JxlSqueezeParameters parameter = ref parameters[i];
CheckMetaSqueezeParameters(parameter, totalNumberOfChannels);
bool horizontal = parameter.Horizontal;
bool inPlace = parameter.InPlace;
int beginC = parameter.BeginC;
int endC = parameter.BeginC + parameter.NumC - 1;
int offset = inPlace
? endC + 1
: totalNumberOfChannels + beginC + endC - 1;
if (beginC < input.MetaChannels)
{
if (input.MetaChannels <= parameter.NumC)
{
throw new InvalidOperationException("Not enough meta channels");
}
input.MetaChannels -= parameter.NumC;
}
for (int c = beginC; c <= endC; c++)
{
int rc = offset + c - beginC;
if (rc >= totalNumberOfChannels)
{
throw new InvalidOperationException("Residual channel offset out of bounds");
}
JxlModularChannel channelC = input.Channels[c]; // Input channel
JxlModularChannel channelRC = input.Channels[rc]; // Residual channel
if (channelC.Width < channelRC.Width || channelC.Height < channelRC.Height)
{
throw new InvalidOperationException("Input channel width or height does not match residual channel width/height");
}
if (horizontal)
{
InverseHorizontalSqueeze(configuration, input, c, rc);
}
else
{
InverseVerticalSqueeze(configuration, input, c, rc);
}
}
}
}
public static void DefaultSqueezeParameters(List<JxlSqueezeParameters> squeezeParameters, JxlModularImage image)
{
int numberOfChannels = image.Channels.Count - image.MetaChannels;
squeezeParameters.Clear();
JxlModularChannel numMetaChannelsChannel = image.Channels[image.MetaChannels];
int w = numMetaChannelsChannel.Width;
int h = numMetaChannelsChannel.Height;
bool wide = w > h;
JxlModularChannel nextNumMetaChannelsChannel = image.Channels[image.MetaChannels + 1];
if (numberOfChannels > 2 && nextNumMetaChannelsChannel.Width == w && nextNumMetaChannelsChannel.Height == h)
{
JxlSqueezeParameters parameters = new()
{
Horizontal = true,
InPlace = false,
BeginC = image.MetaChannels + 1,
NumC = 2
};
squeezeParameters.Add(parameters);
parameters.Horizontal = false;
squeezeParameters.Add(parameters);
}
JxlSqueezeParameters newParameters = new()
{
BeginC = image.MetaChannels,
NumC = numberOfChannels,
InPlace = true
};
if (!wide)
{
if (h > MaxFirstPreviewSize)
{
newParameters.Horizontal = false;
squeezeParameters.Add(newParameters);
h = (h + 1) >> 1;
}
}
while (w > MaxFirstPreviewSize || h > MaxFirstPreviewSize)
{
if (w > MaxFirstPreviewSize)
{
newParameters.Horizontal = true;
squeezeParameters.Add(newParameters);
w = (w + 1) >> 1;
}
if (w > MaxFirstPreviewSize)
{
newParameters.Horizontal = false;
squeezeParameters.Add(newParameters);
h = (h + 1) >> 1;
}
}
}
private static void CheckMetaSqueezeParameters(in JxlSqueezeParameters parameter, int numChannels)
{
int c1 = parameter.BeginC;
int c2 = parameter.BeginC + parameter.NumC - 1;
if (c1 < 0 ||
c1 >= numChannels ||
c2 < 0 ||
c2 >= numChannels ||
c2 < c1)
{
throw new InvalidOperationException("Invalid channel range");
}
}
public static void MetaSqueeze(Configuration configuration, JxlModularImage image, List<JxlSqueezeParameters> parameters)
{
if (parameters.Count == 0)
{
DefaultSqueezeParameters(parameters, image);
}
foreach (JxlSqueezeParameters parameter in parameters)
{
CheckMetaSqueezeParameters(parameter, image.Channels.Count);
bool horizontal = parameter.Horizontal;
bool inPlace = parameter.InPlace;
int beginC = parameter.BeginC;
int endC = parameter.BeginC + parameter.NumC - 1;
if (beginC < image.MetaChannels)
{
if (endC >= image.MetaChannels)
{
throw new InvalidOperationException("Invalid squeeze: mix of meta and nonmeta channels");
}
if (!inPlace)
{
throw new InvalidOperationException("Invalid squeeze: meta channels require in-place residuals");
}
image.MetaChannels += parameter.NumC;
}
int offset = inPlace
? endC + 1
: image.Channels.Count;
for (int c = beginC; c <= endC; c++)
{
JxlModularChannel channel = image.Channels[c];
if (channel.Height > 30 || channel.VerticalShift > 30)
{
throw new InvalidOperationException("Too many squeezes: shift > 30");
}
int w = channel.Width;
int h = channel.Height;
if ((w & h) == 0) // either w, or h, is 0
{
throw new InvalidOperationException("Squeezing empty channel");
}
if (horizontal)
{
channel.Width = (w + 1) >> 1;
if (channel.HorizontalShift >= 0)
{
channel.HorizontalShift++;
}
w -= (w + 1) >> 1;
}
else
{
channel.HorizontalShift = (h + 1) >> 1;
if (channel.VerticalShift >= 0)
{
channel.VerticalShift++;
}
h -= (h + 1) >> 1;
}
channel.Shrink(configuration);
JxlModularChannel placeholder = new(configuration, w, h, channel.HorizontalShift, channel.VerticalShift)
{
Component = channel.Component
};
image.Channels.Insert(offset + (c - beginC), placeholder);
}
}
}
public static void ForwardHorizontalSqueeze(Configuration configuration, JxlModularImage input, int c, int rc)
{
JxlModularChannel inputChannel = input.Channels[c];
// Do not dispose these.
JxlModularChannel outputChannel = new(configuration, (inputChannel.Width + 1) >> 1, inputChannel.Height, inputChannel.HorizontalShift + 1, inputChannel.VerticalShift);
JxlModularChannel outputChannelResidual = new(configuration, inputChannel.Width - outputChannel.Width, outputChannel.Height, inputChannel.HorizontalShift + 1, inputChannel.VerticalShift);
outputChannel.Component = inputChannel.Component;
outputChannelResidual.Component = inputChannel.Component;
for (int y = 0; y < outputChannel.Height; y++)
{
Span<int> pIn = inputChannel.GetRow(y);
Span<int> pOut = outputChannel.GetRow(y);
Span<int> pRes = outputChannelResidual.GetRow(y);
for (int x = 0; x < outputChannelResidual.Width; x++)
{
int x2 = x << 1; // x * 2
int a = pIn[x2];
int b = pIn[x2 + 1];
int avg = Numerics.Average(a, b);
pOut[x] = avg;
int diff = a - b;
int nextAvg = avg;
if (x + 1 < outputChannelResidual.Width)
{
int c2 = pIn[x2 + 2]; // actually C, but 1. variable 'c' already defined 2. names should be camelCase
int d = pIn[x2 + 3];
nextAvg = Numerics.Average(c2, d);
}
else if ((inputChannel.Width & 1) != 0)
{
nextAvg = pIn[x2 + 2];
}
int left = x > 0 ? pIn[x2 - 1] : avg;
int tendency = SmoothTendency(left, avg, nextAvg);
pRes[x] = diff - tendency;
}
if ((inputChannel.Width & 1) != 0)
{
int x = outputChannel.Width - 1;
pOut[x] = pIn[x * 2];
}
}
input.Channels[c] = outputChannel;
input.Channels.Insert(rc, outputChannelResidual);
}
public static void ForwardVerticalSqueeze(Configuration configuration, JxlModularImage input, int c, int rc)
{
JxlModularChannel inputChannel = input.Channels[c];
// Do not dispose these.
JxlModularChannel outputChannel = new(configuration, inputChannel.Width, (inputChannel.Height + 1) >> 1, inputChannel.HorizontalShift, inputChannel.VerticalShift + 1);
JxlModularChannel outputResidualChannel = new(configuration, inputChannel.Width, inputChannel.Height - outputChannel.Height, inputChannel.HorizontalShift, inputChannel.VerticalShift + 1);
outputChannel.Component = inputChannel.Component;
outputResidualChannel.Component = inputChannel.Component;
int oneRowInput = inputChannel.Plane.PixelsPerRow;
for (int y = 0; y < outputChannel.Height; y++)
{
Span<int> pIn = inputChannel.GetRow(y * 2);
Span<int> pOut = outputChannel.GetRow(y);
Span<int> pResidual = outputResidualChannel.GetRow(y);
for (int x = 0; x < outputChannel.Width; x++)
{
int a = pIn[x];
int b = pIn[x + oneRowInput];
int avg = Numerics.Average(a, b);
pOut[x] = avg;
int diff = a - b;
int nextAvg = avg;
if (y + 1 < outputResidualChannel.Height)
{
int c2 = pIn[x + (2 * oneRowInput)]; // actually C, but 1. variable 'c' already defined 2. names should be camelCase
int d = pIn[x + (3 * oneRowInput)];
nextAvg = Numerics.Average(c2, d);
}
else if ((inputChannel.Height & 1) != 0)
{
nextAvg = pIn[x + (2 * oneRowInput)];
}
int top = y > 0 ? pIn[x - oneRowInput] : avg;
int tendency = SmoothTendency(top, avg, nextAvg);
pResidual[x] = diff - tendency;
}
}
if ((inputChannel.Height & 1) != 0)
{
int y = outputChannel.Height - 1;
Span<int> pIn = inputChannel.GetRow(y * 2);
Span<int> pOut = outputChannel.GetRow(y);
for (int x = 0; x < outputChannel.Width; x++)
{
pOut[x] = pIn[x];
}
}
input.Channels[c] = outputChannel;
input.Channels.Insert(rc, outputResidualChannel);
}
public static void ForwardSqueeze(Configuration configuration, JxlModularImage input, List<JxlSqueezeParameters> parameters)
{
if (parameters.Count == 0)
{
DefaultSqueezeParameters(parameters, input);
if (parameters.Count == 0)
{
// If there's nothing to do, don't squeeze.
return;
}
}
foreach (JxlSqueezeParameters parameter in parameters)
{
CheckMetaSqueezeParameters(parameter, input.Channels.Count);
bool horizontal = parameter.Horizontal;
bool inPlace = parameter.InPlace;
int beginC = parameter.BeginC;
int endC = parameter.BeginC + parameter.NumC - 1;
int offset = inPlace
? endC + 1
: input.Channels.Count;
for (int c = beginC; c <= endC; c++)
{
if (horizontal)
{
ForwardHorizontalSqueeze(configuration, input, c, offset + c - beginC);
}
else
{
ForwardVerticalSqueeze(configuration, input, c, offset + c - beginC);
}
}
}
}
}

18
src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlSqueezeParameters.cs

@ -8,12 +8,12 @@ namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Transforms;
/// <summary>
/// Parameters for the squeeze transform.
/// </summary>
internal sealed class JxlSqueezeParameters : IJxlFields
internal struct JxlSqueezeParameters : IJxlFields
{
private bool horizontal;
private bool inPlace;
private uint beginC;
private uint numC;
private int beginC;
private int numC;
public JxlSqueezeParameters() => JxlBundle.Init(this);
@ -22,7 +22,7 @@ internal sealed class JxlSqueezeParameters : IJxlFields
/// </summary>
public bool Horizontal
{
get => this.horizontal;
readonly get => this.horizontal;
set => this.horizontal = value;
}
@ -31,19 +31,19 @@ internal sealed class JxlSqueezeParameters : IJxlFields
/// </summary>
public bool InPlace
{
get => this.inPlace;
readonly get => this.inPlace;
set => this.inPlace = value;
}
public uint BeginC
public int BeginC
{
get => this.beginC;
readonly get => this.beginC;
set => this.beginC = value;
}
public uint NumC
public int NumC
{
get => this.numC;
readonly get => this.numC;
set => this.numC = value;
}

25
src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlTransform.cs

@ -36,4 +36,29 @@ internal sealed class JxlTransform : IJxlFields
}
}
}
public static void ComputeMinMax(JxlModularChannel channel, out int min, out int max)
{
// Start with opposite bounds so the first iteration
// guarantees to set these values
min = int.MaxValue;
max = int.MinValue;
for (int y = 0; y < channel.Height; y++)
{
Span<int> p = channel.GetRow(y);
for (int x = 0; x < channel.Width; x++)
{
if (p[x] < min)
{
min = p[x];
}
if (p[x] > max)
{
max = p[x];
}
}
}
}
}

109
src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/Epf0Stage.cs

@ -0,0 +1,109 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline;
/// <summary>
/// Edge Preserving Filter (type 0) stage
/// </summary>
internal sealed class Epf0Stage : RenderPipelineStageBase
{
private static readonly int[][] SadOffsets =
[
[-2, 0], [-1, -1], [-1, 0], [-1, 1], [0, -2], [0, -1],
[0, 1], [0, 2], [1, -1], [1, 0], [1, 1], [2, 0]
];
private readonly JxlLoopFilter loopFilter;
private readonly JxlImageF sigma;
public Epf0Stage(JxlLoopFilter loopFilter, JxlImageF sigma, Configuration configuration) : base(configuration)
{
this.loopFilter = loopFilter;
this.sigma = sigma;
this.Settings = RenderPipelineStageConfiguration.CreateSymmetricBorderOnly(3);
}
public override string Name => "EPF0";
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AddPixel(
int row,
InlineArray7<InlineArray3<Memory<float>>> rows,
int x,
Vector256<float> sad,
Vector256<float> inverseSigma,
ref Vector256<float> xOut,
ref Vector256<float> yOut,
ref Vector256<float> bOut,
ref Vector256<float> wOut)
{
int rowPlus3 = row + 3;
Vector256<float> cx = Vector256.Create<float>(rows[0][rowPlus3].Span[x..]);
Vector256<float> cy = Vector256.Create<float>(rows[1][rowPlus3].Span[x..]);
Vector256<float> cb = Vector256.Create<float>(rows[2][rowPlus3].Span[x..]);
Vector256<float> weight = EpfUtils.Weight(sad, inverseSigma);
wOut += weight;
xOut += (weight * cx) + xOut;
yOut += (weight * cy) + yOut;
bOut += (weight * cb) + bOut;
}
public override void ProcessRow(Buffer2D<Memory<float>> inputRows, Buffer2D<Memory<float>> outputRows, int xExtraLeft, int xExtraRight, int width, int xPos, int yPos)
{
Span<Vector256<float>> sads = stackalloc Vector256<float>[16].Slice(0, 12);
sads.Clear();
int xStart = -JxlMath.RoundUpTo(xExtraLeft, Vector256<float>.Count);
int xEnd = width + xExtraRight;
Span<float> rowSigma = this.sigma.GetRow((yPos / JxlFrameDimensions.BlockDimensions) + JxlDecoderCache.SigmaPadding);
float sm = this.loopFilter.EpfPass0SigmaScale * 1.65f;
float bsm = sm * this.loopFilter.EpfBorderSadMul;
Span<float> sadMulCenter = [bsm, sm, sm, sm, sm, sm, sm, bsm];
Span<float> sadMulBorder = [bsm, bsm, bsm, bsm, bsm, bsm, bsm, bsm];
int yPosModBlockDim = yPos % JxlFrameDimensions.BlockDimensions;
Span<float> sadMul = yPosModBlockDim is 0 or JxlFrameDimensions.BlockDimensions - 1
? sadMulBorder
: sadMulCenter;
InlineArray3<InlineArray7<Memory<float>>> rows = default;
for (int c = 0; c < 3; c++)
{
for (int i = 0; i < 7; i++)
{
rows[c][i] = this.GetInputRowMemory(inputRows, c, i - 3);
}
}
for (int x = xStart; x < xEnd; x += Vector256<float>.Count)
{
int xPlusXpos = x + xPos;
int bx = (xPlusXpos + (JxlDecoderCache.SigmaPadding * JxlFrameDimensions.BlockDimensions)) / JxlFrameDimensions.BlockDimensions;
int ix = xPlusXpos % JxlFrameDimensions.BlockDimensions;
if (rowSigma[bx] < JxlLoopFilter.MinimumSigma)
{
for (int c = 0; c < 3; c++)
{
Vector256<float> px = Vector256.Create<float>(rows[c][3].Span[x..]);
px.CopyTo(GetOutputRow(outputRows, c, 0)[x..]);
}
continue;
}
Vector256<float> vsm = Vector256.Create<float>(sadMul[ix..]);
Vector256<float> inverseSigma = Vector256.Create<float>(rowSigma[bx]) * vsm;
}
}
}

14
src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/EpfStageType.cs

@ -0,0 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline;
/// <summary>
/// Used by the EPF render pipeline stage.
/// </summary>
internal enum EpfStageType : byte
{
Zero,
One,
Two
}

22
src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/EpfUtils.cs

@ -0,0 +1,22 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline;
/// <summary>
/// Utilities for EPF stages.
/// </summary>
internal static class EpfUtils
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Weight(Vector256<float> sad, Vector256<float> inverseSigma)
{
Vector256<float> v = (sad * inverseSigma) + Vector256<float>.One;
Vector256<float> whereNegative = Vector256.LessThan(v, Vector256<float>.Zero);
Vector256<float> zeroIfNegative = Vector256.ConditionalSelect(whereNegative, Vector256<float>.Zero, whereNegative);
return zeroIfNegative;
}
}

30
src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/RenderPipelineChannelMode.cs

@ -0,0 +1,30 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline;
/// <summary>
/// Specifies how does a render pipeline stage apply to channels.
/// </summary>
internal enum RenderPipelineChannelMode : byte
{
/// <summary>
/// Channel is not modified.
/// </summary>
Ignored,
/// <summary>
/// Channel is in-place.
/// </summary>
InPlace,
/// <summary>
/// Channel is modified and written to a new buffer.
/// </summary>
InOut,
/// <summary>
/// Read-only channel.
/// </summary>
Input
}

92
src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/RenderPipelineStageBase.cs

@ -0,0 +1,92 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline;
/// <summary>
/// Base class for a render pipeline stage.
/// </summary>
[DebuggerDisplay($"{{{nameof(Name)}}}")]
internal abstract class RenderPipelineStageBase(Configuration configuration) : IDisposable
{
private const int RenderPipelineXOffset = 32;
/// <summary>
/// Gets or sets the configuration for this render pipeline stage.
/// </summary>
public RenderPipelineStageConfiguration Settings { get; set; }
/// <summary>
/// Gets a value indicating whether this stage is initialized and is therefore
/// ready to use.
/// </summary>
public virtual bool IsInitialized => true;
/// <summary>
/// Gets a value indicating whether, from this stage on, the pipeline will operate
/// on an image rather than the frame-sized buffer. Only one stage in the pipeline
/// should return true, and it should implement <see cref="ProcessPaddingRow(Buffer2D{Memory{float}}, int, int, int)"/>.
/// </summary>
public virtual bool SwitchToImageDimensions => false;
/// <summary>
/// Gets a friendly name representing this stage.
/// </summary>
public virtual string Name => "(invalid pipeline stage)";
/// <summary>
/// If any unmanaged or pooled memory is present by the derived stage, releases
/// memory used by that.
/// </summary>
public virtual void Dispose()
{
}
public virtual void ProcessRow(
Buffer2D<Memory<float>> inputRows,
Buffer2D<Memory<float>> outputRows,
int xExtraLeft,
int xExtraRight,
int width,
int xPos,
int yPos)
{
}
/// <summary>
/// Represents how each channel will be processed.
/// </summary>
/// <param name="channel">Desired channel.</param>
/// <returns>Mode specifying how the specified channel will be processed.</returns>
public virtual RenderPipelineChannelMode GetChannelMode(int channel)
=> RenderPipelineChannelMode.Ignored;
public virtual void SetInputSizes(Span<Size> inputSizes)
{
}
public Span<float> GetInputRow(Buffer2D<Memory<float>> inputRows, int c, int offset)
=> inputRows[c, this.Settings.BorderY + offset].Span[RenderPipelineXOffset..];
public Memory<float> GetInputRowMemory(Buffer2D<Memory<float>> inputRows, int c, int offset)
=> inputRows[c, this.Settings.BorderY + offset][RenderPipelineXOffset..];
public static Span<float> GetOutputRow(Buffer2D<Memory<float>> outputRows, int c, int offset)
=> outputRows[c, offset].Span[RenderPipelineXOffset..];
public virtual void GetImageDimensions(out int width, out int height, out Point frameOrigin)
{
width = 0;
height = 0;
frameOrigin = default;
}
public virtual void ProcessPaddingRow(Buffer2D<Memory<float>> outputRows, int width, int xPos, int yPos)
{
}
protected Configuration GetConfiguration() => configuration;
}

21
src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/RenderPipelineStageConfiguration.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline;
internal record struct RenderPipelineStageConfiguration(int BorderX, int BorderY, int ShiftX, int ShiftY)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RenderPipelineStageConfiguration CreateShiftX(int shift, int border) => new(border, 0, shift, 0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RenderPipelineStageConfiguration CreateShiftY(int shift, int border) => new(0, border, 0, shift);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RenderPipelineStageConfiguration CreateSymmetric(int shift, int border) => new(border, border, shift, shift);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RenderPipelineStageConfiguration CreateSymmetricBorderOnly(int border) => CreateSymmetric(shift: 0, border);
}

2
src/ImageSharp/Formats/Jxl/Processing/Splines/JxlSplineSegment.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Splines;
internal struct JxlSplineSegment

14
src/ImageSharp/ImageSharp.csproj

@ -44,6 +44,11 @@
<None Include="..\..\LICENSE" Pack="true" PackagePath="" />
<None Include="..\..\shared-infrastructure\branding\icons\imagesharp\sixlabors.imagesharp.128.png" Pack="true" PackagePath="" />
<None Include="..\..\SixLabors.ImageSharp.props" Pack="true" PackagePath="build" />
<None Include="Formats\Jxl\Processing\JxlSimdUtils.StoreInterleaved.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>JxlSimdUtils.StoreInterleaved.tt</DependentUpon>
</None>
</ItemGroup>
<ItemGroup>
@ -57,6 +62,11 @@
<AutoGen>True</AutoGen>
<DependentUpon>InlineArray.tt</DependentUpon>
</Compile>
<Compile Update="Formats\Jxl\Processing\JxlSimdUtils.StoreInterleaved.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>JxlSimdUtils.StoreInterleaved.tt</DependentUpon>
</Compile>
<Compile Update="Formats\_Generated\ImageExtensions.Save.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@ -164,6 +174,10 @@
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>InlineArray.cs</LastGenOutput>
</None>
<None Update="Formats\Jxl\Processing\JxlSimdUtils.StoreInterleaved.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>JxlSimdUtils.StoreInterleaved.Generated.cs</LastGenOutput>
</None>
<None Update="Formats\_Generated\ImageMetadataExtensions.tt">
<LastGenOutput>ImageMetadataExtensions.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>

48
tests/ImageSharp.Tests/Common/Vector256UtilitiesTests.cs

@ -0,0 +1,48 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Tests.Common;
public class Vector256UtilitiesTests
{
[Theory]
[InlineData(new int[] { 4, 5, 6, 7, 8, 9, 10, 11 }, new int[] { 1, 2, 3, 4, 0, -1, -2, -3 }, new int[] { 4, 1, 5, 2, 6, 3, 7, 4 })]
public void TestVector256InterleaveLower(int[] a, int[] b, int[] expected)
{
Vector256<int> v256a = Vector256.Create(a);
Vector256<int> v256b = Vector256.Create(b);
Vector256<int> v256 = Vector256_.InterleaveLower(v256a, v256b);
int[] result = new int[Vector256<int>.Count];
v256.CopyTo(result);
bool isEqual = expected.SequenceEqual(result);
if (!isEqual)
{
Assert.Fail($"Lower shuffle failed.\n\nExpected: [{string.Join(", ", expected)}]\nActual: [{string.Join(", ", result)}]");
}
}
[Theory]
[InlineData(new int[] { 4, 5, 6, 7, 8, 9, 10, 11 }, new int[] { 1, 2, 3, 4, 0, -1, -2, -3 }, new int[] { 8, 0, 9, -1, 10, -2, 11, -3 })]
public void TestVector256InterleaveUpper(int[] a, int[] b, int[] expected)
{
Vector256<int> v256a = Vector256.Create(a);
Vector256<int> v256b = Vector256.Create(b);
Vector256<int> v256 = Vector256_.InterleaveUpper(v256a, v256b);
int[] result = new int[Vector256<int>.Count];
v256.CopyTo(result);
bool isEqual = expected.SequenceEqual(result);
if (!isEqual)
{
Assert.Fail($"Lower shuffle failed.\n\nExpected: [{string.Join(", ", expected)}]\nActual: [{string.Join(", ", result)}]");
}
}
}
Loading…
Cancel
Save