Skip to content

Support for JPEG XL (JXL) images - #3153

Draft
winscripter wants to merge 72 commits into
SixLabors:mainfrom
winscripter:jxl-support
Draft

Support for JPEG XL (JXL) images#3153
winscripter wants to merge 72 commits into
SixLabors:mainfrom
winscripter:jxl-support

Conversation

@winscripter

Copy link
Copy Markdown

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

This is a work-in-progress PR whose goal is to introduce decoding and encoding of JPEG XL (*.jxl) images.

Reference software
I use libjxl as reference. See https://github.com/libjxl/libjxl.

Performance
I will begin by applying light optimizations as I implement parts of the JPEG XL codec. Once the codec seems complete enough to handle decoding and encoding of JPEG XL images, I will apply heavier optimizations. Examples include but are not limited to stack allocation, array pooling, and SIMD.

Other components
The JPEG XL codec, additionally, uses the LZ77 and Brotli compression codec. I will also have to implement those eventually.

Implementations
The JPEG XL codec lives under src/ImageSharp/Formats/Jxl.

Brotli and LZ77 implementations will live under src/ImageSharp/Compression.

Testing
I will start adding tests whenever the codec is complete enough to handle decoding of JPEG XL images.

Additionally, JPEG XL reference software, libjxl, contains its own tests too, which I might also implement without modification.

@CLAassistant

CLAassistant commented Jul 15, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Implementation of ac_strategy.h and ac_strategy.c
For now JxlMemoryManager will be a wrapper around MemoryPool<T>.
Implementation of image.h and image.c; AC strategy implementation was slightly adjusted to reduce errors.
This is an implementation of field_encodings.h.

Note that I avoided implementing EnumValid() and Values() functions, as we have dedicated methods in .NET to do exactly that (Enum.IsDefined, Enum.GetValues)
Implementation of spline.h
Implemented ANS constants
@winscripter

Copy link
Copy Markdown
Author

While I'm working on this, I'd like to note something important.

Libjxl is licensed under the BSD 3-Clause license, and since I'm using libjxl code as reference, that means the license must be included.

I'm not really sure what would be the proper way to include the license. I might place the LICENSE.txt file in the Jxl folder or add a README linking to the libjxl repo.

Comment thread src/ImageSharp/Formats/Jxl/Metadata/JxlExifOrientation.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/Metadata/JxlExtraChannel.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/Splines/JxlSplineEntropyContext.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/JxlFrameDimensions.cs Outdated
It is too large for a struct.
Add JxlAnsEntry and JxlAnsSymbol.

See ans_common.h. These correspond to the Entry and Symbol structures within AliasTable.
Comment thread src/ImageSharp/Formats/Jxl/IO/JxlAnsHelper.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/IO/JxlAnsHelper.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/JxlThrowHelper.cs Outdated
Currently, there's a VarLenUint8/VarLenUint16 as well as histogram parsing implementation.

I will additionally have to implement parsing of ANS codes, uint config and LZ77 parameters.
Comment thread src/ImageSharp/Formats/Jxl/Processing/Butteraugli/ButteraugliParameters.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/Processing/Butteraugli/Butteraugli.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/Processing/Butteraugli/Butteraugli.cs
Comment thread src/ImageSharp/Formats/Jxl/Processing/JxlQuantizer.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/Processing/JxlQuantizer.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/IO/Metadata/JxlOpsinInverseMatrix.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlOpsinParameters.cs Outdated

public static bool ReadPermutation(int skip, int size, Span<int> order, JxlBitReader bitReader, JxlAnsSymbolReader reader, Span<byte> contextMap)
{
Span<uint> lehmer = stackalloc uint[size];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is size limited to some maximum value?
If so, there should be an assert here to make it clear.

For the stackalloc it's better to use a constant value, then slice it if needed. Thus produces most of the time better code. E.g. stackalloc uint[128].Slice(0, size).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although neither the reference implementation nor this implementation explicitly bounds size here, it is constrained by the JPEG XL bitstream syntax. size is derived from the transform strategy and can only take one of the specification-defined coefficient counts (64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, or 65536). The maximum possible value is therefore 65536.

I'll add an assert.

Comment thread src/ImageSharp/Formats/Jxl/Processing/JxlCoefficientOrder.cs
Comment thread src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs Outdated
Comment thread src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs Outdated
- Add decoding of Huffman Codes (see dec_huffman.cc and dec_huffman.h)
- Add constructors to JxlImage3* classes
- Make JxlColorCorrelationMap.Create 'xyb' parameter use true as a default value
- Prototype of DCT quant weight parameters
- Add passes shared state (see passes_state.cc and passes_state.h)
- Add prototype for image operations (see image_ops.cc and image_ops.h)
- Simplify inverse MTF (Move to Front) transform
- Add patch context (see patch_dictionary_internal.h)
- Add prototype of quantizer weights
- Add 2nd prototype of ANS entropy decoding (see dec_ans.cc and dec_ans.h)
- Add prototype of patch dictionary decoding (see dec_patch_dictionary.cc and dec_patch_dictionary.h)
This massively reduces number of syntax errors
Comment on lines +58 to +59
// Use new because there's only up to 3 elements
float[] kernel = new float[(2 * diff) + 1];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only up to 3 elements

Use an inline-array then?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad - I misread the Math.Max call and thought it was capping diff at 1 rather than setting its minimum. So the kernel can be well over 3 elements depending on sigma. It still shouldn't get very large in practice, so I'll add a temporary DebugGuard to cap it at 32 while I verify the expected range.

Comment on lines +2098 to +2099
JxlImageF[] hf0 = new JxlImageF[2];
JxlImageF[] hf1 = new JxlImageF[2];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these allocations be avoided (inline arrays)?

Comment on lines +2156 to +2157
JxlImageF[] uhf0 = new JxlImageF[2];
JxlImageF[] uhf1 = new JxlImageF[2];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also inline arrays?

Comment on lines +106 to +107
Span<uint> symbols = stackalloc uint[2];
symbols.Clear();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For such small stack-allocs

Suggested change
Span<uint> symbols = stackalloc uint[2];
symbols.Clear();
Span<uint> symbols = [0, 0];

is

  • the same semantics
  • easier to read
  • avoids the stack cookie that the JIT emits for stack-safety validation (which by using the inline array via collection expression the C# compiler does)
  • note: for small sizes Clear is unrolled by the JIT, so no actual method call needed

Comment on lines +272 to +273
// The alphabet size is at most 256
Span<byte> codeLengths = stackalloc byte[alphabetSize];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// The alphabet size is at most 256
Span<byte> codeLengths = stackalloc byte[alphabetSize];
Debug.Assert(alphabetSize <= 256);
Span<byte> codeLengths = stackalloc byte[256].Slice(0, alphabetSize);

stack-allocating to a constant produces better code. Then slicing it is also cheaper than stackalloc byte[variable].

{
this.memoryOwner?.Dispose();
this.ControlPoints = Memory<JxlControlPoint>.Empty;
GC.SuppressFinalize(this);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
GC.SuppressFinalize(this);

Not needed. This is internal sealed class, so nothing can derive from it and therefore nothing can put a finalizer to this object.

I think I've spotted similar calls above, but didn't flag them. Please revise these.

Comment on lines +349 to +359
while (x < 0 || x >= xSize)
{
if (x < 0)
{
x = -x - 1;
}
else
{
x = (2 * xSize) - 1 - x;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
while (x < 0 || x >= xSize)
{
if (x < 0)
{
x = -x - 1;
}
else
{
x = (2 * xSize) - 1 - x;
}
}
while (true)
{
if (x < 0)
{
x = -x - 1;
}
else if (x >= xSize)
{
x = (2 * xSize) - 1 - x;
}
else
{
break;
}
}

should produce a tighter loop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants