Skip to content
Draft
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
85c851b
Add JPEG XL format
winscripter Jul 14, 2026
996e93d
Implement ac_context.h
winscripter Jul 14, 2026
d87178f
Implemented frame_dimensions.h plus part of ac_strategy.h
winscripter Jul 14, 2026
1ac6140
Implement AC strategy
winscripter Jul 15, 2026
31b5505
Implement JxlMemoryManager
winscripter Jul 15, 2026
21ff33f
Implement image and memory buffers
winscripter Jul 15, 2026
6fea920
Add metadata models
winscripter Jul 15, 2026
762edd1
Add headers & aspect ratio helpers
winscripter Jul 15, 2026
487410e
Implement field encodings, move IJxlFields
winscripter Jul 15, 2026
ac1c866
Add xorshift implemetation
winscripter Jul 15, 2026
8581222
It's Jxl not Jpeg
winscripter Jul 15, 2026
9b58f4b
Implement spline abstractions
winscripter Jul 15, 2026
064376a
Add quantized spline implementations
winscripter Jul 16, 2026
90680b7
Prefer the term "coefficient"
winscripter Jul 16, 2026
1e40482
Start work on ANS entropy
winscripter Jul 16, 2026
396cd8d
Implement JxlAnsHelper.GetPopulationCountPrecision
winscripter Jul 16, 2026
cb364fa
Turn JxlFrameDimensions into a sealed class
winscripter Jul 16, 2026
528149f
Implement CreateFlatHistogram
winscripter Jul 16, 2026
490f02f
Add ANS entropy structs
winscripter Jul 16, 2026
73941c1
Prefer byte for enums
winscripter Jul 16, 2026
20c2dd3
Implement ANS entropy helpers
winscripter Jul 16, 2026
fed0af3
Add bit-stream reader
winscripter Jul 16, 2026
25e41da
Avoid 'using static'
winscripter Jul 16, 2026
4ec9b95
Simplify
winscripter Jul 16, 2026
b716d4e
Add ANS VarLen & histogram parser
winscripter Jul 16, 2026
1381def
Fix memory leaks
winscripter Jul 16, 2026
8ffb41a
Don't use end of stream singleton
winscripter Jul 16, 2026
3ed56c9
Add ANS hybrid uint configuration & LZ77 parameters model
winscripter Jul 16, 2026
7f35b1a
Implement Lehmer codes
winscripter Jul 16, 2026
57eadbb
Implement noise shared logic
winscripter Jul 16, 2026
6de4a89
Add alpha blending
winscripter Jul 16, 2026
289fecd
Move AC strategy & Coefficients stuff into Processing
winscripter Jul 17, 2026
b15c190
Add symmetric weights; fix broken using
winscripter Jul 17, 2026
b3023f2
Add separable weights
winscripter Jul 17, 2026
73c7cec
Add DCT scales
winscripter Jul 17, 2026
7223d98
Add DCT memory, make JxlImage3<T> implement IDisposable
winscripter Jul 17, 2026
51b7765
Implement signed packing
winscripter Jul 17, 2026
446e4a5
Add loop filter
winscripter Jul 18, 2026
decc0b2
Fully implement JPEG XL fields
winscripter Jul 18, 2026
a6a0263
Implement visitor for JxlBitDepth
winscripter Jul 18, 2026
6aa313f
Add matrices
winscripter Jul 18, 2026
d401b71
Fix errors
winscripter Jul 18, 2026
a803f01
Add Y'Cb'Cr chroma subsampling as part of the frame header
winscripter Jul 18, 2026
16c55f3
Add frame header
winscripter Jul 19, 2026
ded8906
Move Metadata folder to IO
winscripter Jul 19, 2026
674f82a
Add quantizer
winscripter Jul 20, 2026
07f6be0
Fix memory leak
winscripter Jul 20, 2026
b720196
Add opsin inverse parameters, some quantizer weight work
winscripter Jul 20, 2026
2758454
Move, add entropy coding common functions
winscripter Jul 21, 2026
6eda7e2
Remove aggressive inlining attributes, fix usings
winscripter Jul 21, 2026
f71bab2
Use distinct read-only spans
winscripter Jul 21, 2026
abafaf8
Add CFL
winscripter Jul 21, 2026
9c6b4ef
Add Huffman common methods
winscripter Jul 22, 2026
3591250
Start work on Butteraugli
winscripter Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/ImageSharp/Formats/Jxl/Ac/JxlAcContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Diagnostics;
using System.Runtime.CompilerServices;

#pragma warning disable SA1405 // Debug.Assert should provide message text

namespace SixLabors.ImageSharp.Formats.Jxl.Ac;

/// <summary>
/// AC context
/// </summary>
internal static class JxlAcContext
{
public const int DctOrderContextStart = 0;
public const int NonZeroBuckets = 37;
public const int ZeroDensityContextCount = 458;
public const int ZeroDensityContextLimit = 474;

public static ReadOnlySpan<int> CoefficientFrequencyContext =>
[
0xBAD, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26,
27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30,
];

public static ReadOnlySpan<int> CoefficientNumNonzeroContext =>
[
0xBAD, 0, 31, 62, 62, 93, 93, 93, 93, 123, 123, 123, 123,
152, 152, 152, 152, 152, 152, 152, 152, 180, 180, 180, 180, 180,
180, 180, 180, 180, 180, 180, 180, 206, 206, 206, 206, 206, 206,
206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
];

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ZeroDensityContext(int nonZeroesLeft, int k, int coveredBlocks, int log2CoveredBlocks, int prev)
{
Debug.Assert((1 << log2CoveredBlocks) == coveredBlocks);

nonZeroesLeft = (nonZeroesLeft + coveredBlocks - 1) >> log2CoveredBlocks;
k >>= log2CoveredBlocks;

Debug.Assert(k > 0);
Debug.Assert(k < 64);
Debug.Assert(nonZeroesLeft > 0);
Debug.Assert(nonZeroesLeft < 64);

return ((CoefficientNumNonzeroContext[nonZeroesLeft] + CoefficientFrequencyContext[k]) * 2) + prev;
}
}
186 changes: 186 additions & 0 deletions src/ImageSharp/Formats/Jxl/Ac/JxlAcStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static SixLabors.ImageSharp.Formats.Jxl.JxlFrameDimensions;

#pragma warning disable SA1405 // Debug.Assert should provide message text

namespace SixLabors.ImageSharp.Formats.Jxl.Ac;

[StructLayout(LayoutKind.Sequential, Pack = 8)]
internal struct JxlAcStrategy
{
public const int MaximumCoefficientBlocks = 32;
public const int MaximumBlockDimension = BlockDimensions * MaximumCoefficientBlocks;
public const int MaximumCoefficientArea = MaximumBlockDimension * MaximumBlockDimension;
public const int NumberOfValidStrategies = 27;

private static readonly int MultiblockBits =
GetTypeBit(JxlAcStrategyType.DCT16X16) | GetTypeBit(JxlAcStrategyType.DCT32X32) |
GetTypeBit(JxlAcStrategyType.DCT16X8) | GetTypeBit(JxlAcStrategyType.DCT8X16) |
GetTypeBit(JxlAcStrategyType.DCT32X8) | GetTypeBit(JxlAcStrategyType.DCT8X32) |
GetTypeBit(JxlAcStrategyType.DCT16X32) | GetTypeBit(JxlAcStrategyType.DCT32X16) |
GetTypeBit(JxlAcStrategyType.DCT32X64) | GetTypeBit(JxlAcStrategyType.DCT64X32) |
GetTypeBit(JxlAcStrategyType.DCT64X64) | GetTypeBit(JxlAcStrategyType.DCT64X128) |
GetTypeBit(JxlAcStrategyType.DCT128X64) |
GetTypeBit(JxlAcStrategyType.DCT128X128) |
GetTypeBit(JxlAcStrategyType.DCT128X256) |
GetTypeBit(JxlAcStrategyType.DCT256X128) |
GetTypeBit(JxlAcStrategyType.DCT256X256);

private readonly bool isFirst;

public JxlAcStrategy(JxlAcStrategyType strategy, bool isFirst)
{
this.Strategy = strategy;
this.isFirst = isFirst;

Debug.Assert(this.IsMultiblock);
}

public JxlAcStrategy(JxlAcStrategyType strategy)
: this(strategy, true)
{
}

public JxlAcStrategy(int rawStrategy)
: this((JxlAcStrategyType)rawStrategy)
{
}

private static ReadOnlySpan<byte> CoveredBlocksXLookup =>
[
1, 1, 1, 1, 2, 4, 1, 2, 1,
4, 2, 4, 1, 1, 1, 1, 1, 1,
8, 4, 8, 16, 8, 16, 32, 16, 32
];

private static ReadOnlySpan<byte> CoveredBlocksYLookup =>
[
1, 1, 1, 1, 2, 4, 2, 1, 4,
1, 4, 2, 1, 1, 1, 1, 1, 1,
8, 8, 4, 16, 16, 8, 32, 32, 16
];

private static ReadOnlySpan<byte> Log2CoveredBlocksLookup =>
[
0, 0, 0, 0, 2, 4, 1, 1, 2,
2, 3, 3, 0, 0, 0, 0, 0, 0,
6, 5, 5, 8, 7, 7, 10, 9, 9
];

public readonly bool IsMultiblock => ((1 << (int)this.Strategy) & MultiblockBits) != 0;

public readonly int RawStrategy => (int)this.Strategy;

public readonly int CoveredBlocksX => CoveredBlocksXLookup[(int)this.Strategy];

public readonly int CoveredBlocksY => CoveredBlocksYLookup[(int)this.Strategy];

public readonly int Log2CoveredBlocks => Log2CoveredBlocksLookup[(int)this.Strategy];

public readonly JxlAcStrategyType Strategy { get; }

public void ComputeNaturalCoefficientOrder(ref int order) => CoefficientOrderAndLookup(this, false, ref order);

public void ComputeNaturalCoefficientOrderLookup(ref int lookup) => CoefficientOrderAndLookup(this, true, ref lookup);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetTypeBit(JxlAcStrategyType type) => 1 << (int)type;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsRawStrategyValid(int rawStrategy) => rawStrategy is < NumberOfValidStrategies and >= 0;

private static void CoefficientOrderAndLookup(JxlAcStrategy strategy, bool isLookup, Span<int> output)
{
// TODO: CoefficientLayout
// TODO: CeilLog2Nonzero
int cx = strategy.CoveredBlocksX;
int cy = strategy.CoveredBlocksY;

CoefficientLayout(ref cx, ref cy);

int xs = cx / cy;
int xsm = xs - 1;
int xss = CeilLog2Nonzero(xs);
int cur = cx * cy;

for (int i = 0; i < cx * BlockDimensions; i++)
{
for (int j = 0; j <= i; j++)
{
int x = j;
int y = i - j;

if ((i & 1) == 0)
{
// swap
(x, y) = (y, x);
}

if ((y & xsm) != 0)
{
continue;
}

y >>= xss;
int value = 0;

if (x < cx && y < cy)
{
value = (y * cx) + x;
}
else
{
value = cur++;
}

if (isLookup)
{
output[((y * cx) * BlockDimensions) + x] = value;
}
else
{
output[value] = ((y * cx) * BlockDimensions) + x;
}
}
}

for (int ip = (cx * BlockDimensions) - 1; ip > 0; ip--)
{
int i = ip - 1;

for (int j = 0; j <= i; j++)
{
int x = (cx * BlockDimensions) - 1 - (i - j);
int y = (cx * BlockDimensions) - 1 - j;

if ((i & 1) != 0)
{
// swap
(x, y) = (y, x);
}

if ((y & xsm) != 0)
{
continue;
}

y >>= xss;
int value = cur++;

if (isLookup)
{
output[((y * cx) * BlockDimensions) + x] = value;
}
else
{
output[value] = ((y * cx) * BlockDimensions) + x;
}
}
}
}
}
119 changes: 119 additions & 0 deletions src/ImageSharp/Formats/Jxl/Ac/JxlAcStrategyImage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Diagnostics;
using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes;

namespace SixLabors.ImageSharp.Formats.Jxl.Ac;

internal sealed class JxlAcStrategyImage : IDisposable
{
private const byte Invalid = byte.MaxValue; // 255

private JxlImageB? layers;
private Memory<byte> row;
private int stride;

public int XSize => this.layers!.XSize;

public int YSize => this.layers!.YSize;

public int PixelsPerRow => this.layers!.PixelsPerRow;

public JxlAcStrategyRow GetRow(int y, int xPrefix = 0)
{
ReadOnlyMemory<byte> layerRow = this.layers!.GetRowBytesMemory(y);
ReadOnlyMemory<byte> row = layerRow[xPrefix..];

return new JxlAcStrategyRow(row);
}

public static JxlAcStrategyImage Create(Configuration memoryManager, int xSize, int ySize)
{
JxlAcStrategyImage image = new()
{
layers = new JxlImageB(memoryManager, xSize, ySize)
};

image.row = image.layers.GetRowBytesMemory(0);
image.stride = image.layers.PixelsPerRow;

return image;
}

public int CountBlocks(JxlAcStrategyType type)
{
int value = 0;
int compare = ((int)type << 1) | 1;

for (int y = 0; y < this.layers!.YSize; y++)
{
ReadOnlySpan<byte> row = this.layers!.GetRow(y);

for (int x = 0; x < this.layers!.XSize; x++)
{
if (row[x] == compare)
{
value++;
}
}
}

return value;
}

public JxlAcStrategyRow GetRow(in Rectangle rect, int y) => this.GetRow(rect.Y + y, rect.X);

public bool IsValid(int x, int y) => this.row.Span[(y * this.stride) + x] != Invalid;

public bool SetNoBoundsChecks(int x, int y, JxlAcStrategyType type, bool check = true)
{
JxlAcStrategy strategy = new(type);
Span<byte> rowSpan = this.row.Span;
int rawType = (int)type;
int rawTypeTimes2 = rawType << 1;

for (int iy = 0; iy < strategy.CoveredBlocksX; iy++)
{
for (int ix = 0; ix < strategy.CoveredBlocksX; ix++)
{
int pos = ((y + iy) * this.stride) + x + ix;

if (check && rowSpan[pos] != Invalid)
{
Debug.Fail("Invalid AC strategy. Blocks overlap.");

return false;
}

rowSpan[pos] = (byte)(rawTypeTimes2 | ((iy | ix) == 0 ? 1 : 0));
}
}

return true;
}

public bool Set(int x, int y, JxlAcStrategyType type)
{
#if DEBUG
JxlAcStrategy strategy = new(type);

Debug.Assert(y + strategy.CoveredBlocksY <= this.layers!.YSize, "Invalid range");
Debug.Assert(x + strategy.CoveredBlocksX <= this.layers.XSize, "Invalid range");
#endif

return this.SetNoBoundsChecks(x, y, type, check: false);
}

public void FillDct8(in Rectangle rect) => this.FillPlane(((int)JxlAcStrategyType.DCT << 1) | 1, this.layers, in rect);

public void FillDct8() => this.FillDct8(in this.layers.GetRectangle());

public void FillInvalid() => this.FillImage(Invalid, this.layers);

public void Dispose()
{
this.layers?.Dispose();
GC.SuppressFinalize(this);
}
}
31 changes: 31 additions & 0 deletions src/ImageSharp/Formats/Jxl/Ac/JxlAcStrategyRow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace SixLabors.ImageSharp.Formats.Jxl.Ac;

internal sealed class JxlAcStrategyRow
{
private readonly ReadOnlyMemory<byte> row;

public JxlAcStrategyRow(ReadOnlyMemory<byte> row) => this.row = row;

public JxlAcStrategy this[int x]
{
get
{
ReadOnlySpan<byte> span = this.row.Span;

Debug.Assert(x * 8 < span.Length, "Too many bytes of memory were requested");

ref byte first = ref MemoryMarshal.GetReference(span);
JxlAcStrategyType strategy = (JxlAcStrategyType)(Unsafe.Add(ref Unsafe.As<byte, int>(ref first), x) >> 1);
bool isFirst = Unsafe.Add(ref first, x) != 0;

return new JxlAcStrategy(strategy, isFirst);
}
}
}
Loading