mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
66 changed files with 3499 additions and 0 deletions
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Ac; |
|||
|
|||
internal enum JxlAcStrategyType : ushort |
|||
{ |
|||
// Regular block size DCT
|
|||
DCT = 0, |
|||
|
|||
// Encode pixels without transforming
|
|||
IDENTITY = 1, |
|||
|
|||
// Use 2-by-2 DCT
|
|||
DCT2X2 = 2, |
|||
|
|||
// Use 4-by-4 DCT
|
|||
DCT4X4 = 3, |
|||
|
|||
// Use 16-by-16 DCT
|
|||
DCT16X16 = 4, |
|||
|
|||
// Use 32-by-32 DCT
|
|||
DCT32X32 = 5, |
|||
|
|||
// Use 16-by-8 DCT
|
|||
DCT16X8 = 6, |
|||
|
|||
// Use 8-by-16 DCT
|
|||
DCT8X16 = 7, |
|||
|
|||
// Use 32-by-8 DCT
|
|||
DCT32X8 = 8, |
|||
|
|||
// Use 8-by-32 DCT
|
|||
DCT8X32 = 9, |
|||
|
|||
// Use 32-by-16 DCT
|
|||
DCT32X16 = 10, |
|||
|
|||
// Use 16-by-32 DCT
|
|||
DCT16X32 = 11, |
|||
|
|||
// 4x8 and 8x4 DCT
|
|||
DCT4X8 = 12, |
|||
DCT8X4 = 13, |
|||
|
|||
// Corner-DCT.
|
|||
AFV0 = 14, |
|||
|
|||
AFV1 = 15, |
|||
AFV2 = 16, |
|||
AFV3 = 17, |
|||
|
|||
// Larger DCTs
|
|||
DCT64X64 = 18, |
|||
DCT64X32 = 19, |
|||
DCT32X64 = 20, |
|||
|
|||
// No transforms smaller than 64x64 are allowed below.
|
|||
DCT128X128 = 21, |
|||
DCT128X64 = 22, |
|||
DCT64X128 = 23, |
|||
DCT256X256 = 24, |
|||
DCT256X128 = 25, |
|||
DCT128X256 = 26 |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Coefficients; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Ac; |
|||
|
|||
internal sealed class JxlBlockContextMap |
|||
{ |
|||
public JxlBlockContextMap() |
|||
{ |
|||
DefaultContextMap.CopyTo(this.ContextMap); |
|||
this.ContextCount = this.ContextMap.Max() + 1; |
|||
this.DcContextCount = 1; |
|||
} |
|||
|
|||
private static ReadOnlySpan<byte> DefaultContextMap => |
|||
[ |
|||
0, 1, 2, 2, 3, 3, 4, 5, 6, 6, 6, 6, 6, |
|||
7, 8, 9, 9, 10, 11, 12, 13, 14, 14, 14, 14, 14, |
|||
7, 8, 9, 9, 10, 11, 12, 13, 14, 14, 14, 14, 14, |
|||
]; |
|||
|
|||
public List<int>[] DcThresholds { get; } = [[], [], []]; |
|||
|
|||
public List<uint> QfThresholds { get; } = []; |
|||
|
|||
public byte[] ContextMap { get; } = new byte[DefaultContextMap.Length]; |
|||
|
|||
public int ContextCount { get; set; } |
|||
|
|||
public int DcContextCount { get; set; } |
|||
|
|||
public int AcContextCount => (this.ContextCount * JxlAcContext.NonZeroBuckets) + JxlAcContext.ZeroDensityContextCount; |
|||
|
|||
public int Context(int dcIndex, uint qf, int ord, int c) |
|||
{ |
|||
int qfIndex = 0; |
|||
for (int i = 0; i < this.QfThresholds.Count; i++) |
|||
{ |
|||
uint t = this.QfThresholds[i]; |
|||
|
|||
if (qf > t) |
|||
{ |
|||
qfIndex++; |
|||
} |
|||
} |
|||
|
|||
int idx = c < 2 ? c ^ 1 : 2; |
|||
idx = (idx * JxlForwardCoefficientOrder.OrderCount) + ord; |
|||
idx = (idx * (this.QfThresholds.Count + 1)) + qfIndex; |
|||
idx = (idx * this.DcContextCount) + dcIndex; |
|||
return this.ContextMap[idx]; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public int ZeroDensityContextOffset(int blockContext) => |
|||
(this.ContextCount * JxlAcContext.NonZeroBuckets) + JxlAcContext.ZeroDensityContextCount + blockContext; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public int NonZeroContext(int nonZeroes, int blockContext) |
|||
{ |
|||
if (nonZeroes >= 64) |
|||
{ |
|||
nonZeroes = 64; |
|||
} |
|||
|
|||
int ctx = nonZeroes < 8 ? nonZeroes : (4 + (nonZeroes / 2)); |
|||
return (ctx * this.ContextCount) + blockContext; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Coefficients; |
|||
|
|||
internal static class JxlForwardCoefficientOrder |
|||
{ |
|||
public const byte OrderCount = 13; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int CoefficientRows(int rows, int columns) => rows < columns ? rows : columns; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int CoefficientColumns(int rows, int columns) => rows < columns ? columns : rows; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void CoefficientLayout(ref int rows, ref int columns) |
|||
{ |
|||
rows = CoefficientRows(rows, columns); |
|||
columns = CoefficientColumns(rows, columns); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
/// <summary>
|
|||
/// Abstracts enumeration of all fields into a visitor.
|
|||
/// </summary>
|
|||
internal interface IJxlFields |
|||
{ |
|||
/// <summary>
|
|||
/// Visits all fields into the specified JXL visitor.
|
|||
/// </summary>
|
|||
/// <param name="visitor">The visitor to use to visit all fields.</param>
|
|||
/// <returns>Status of the visit operation.</returns>
|
|||
public bool Visit(JxlVisitor visitor); |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
internal static class JxlFieldExpressions |
|||
{ |
|||
public static JxlU32Distribution Value(uint value) |
|||
{ |
|||
const uint directConstant = JxlU32Distribution.DirectConstant; |
|||
|
|||
return new(value | directConstant); |
|||
} |
|||
|
|||
public static JxlU32Distribution BitsOffset(uint bits, uint offset) |
|||
=> new(((bits - 1u) & 0x1Fu) + ((offset & 0x3FFFFFFu) << 5)); |
|||
|
|||
public static JxlU32Distribution Bits(uint value) => BitsOffset(value, 0u); |
|||
|
|||
public static int MakeBit(int index) => 1 << index; |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
internal struct JxlU32Distribution(uint d) |
|||
{ |
|||
public const uint DirectConstant = 0x80000000u; |
|||
|
|||
public readonly bool IsDirect => (d & DirectConstant) != 0; |
|||
|
|||
public readonly uint Direct => d & (DirectConstant - 1u); |
|||
|
|||
public readonly uint ExtraBits => (d & 0x1Fu) + 1u; |
|||
|
|||
public readonly uint Offset => (d >> 5) & 0x3FFFFFF; |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
internal readonly struct JxlU32Enc |
|||
{ |
|||
private readonly InlineArray4<JxlU32Distribution> d = default; |
|||
|
|||
public JxlU32Enc(JxlU32Distribution d0, JxlU32Distribution d1, JxlU32Distribution d2, JxlU32Distribution d3) |
|||
{ |
|||
this.d[0] = d0; |
|||
this.d[1] = d1; |
|||
this.d[2] = d2; |
|||
this.d[3] = d3; |
|||
} |
|||
|
|||
public JxlU32Distribution GetDistribution(int selector) |
|||
{ |
|||
Debug.Assert(selector < 4, "Selector out of range"); |
|||
|
|||
return this.d[selector]; |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
internal static class JxlAnsConstants |
|||
{ |
|||
public const int AnsLogTableSize = 12; |
|||
public const int AnsTableSize = 1 << AnsLogTableSize; |
|||
public const int AnsTabMask = AnsTableSize - 1; |
|||
public const int PrefixMaxAlphabetSize = 4096; |
|||
public const int AnsMaxAlphabetSize = 256; |
|||
public const int PrefixMaxBits = 15; |
|||
public const int AnsSignature = 0x13; |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct JxlAnsEntry |
|||
{ |
|||
public byte Cutoff; |
|||
public byte RightValue; |
|||
public ushort Frequency0; |
|||
public ushort Offsets1; |
|||
public ushort Frequency1XorFrequency0; |
|||
} |
|||
@ -0,0 +1,242 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Diagnostics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
internal static class JxlAnsHelper |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetPopulationCountPrecision(int logCount, int shift) |
|||
=> Math.Max(0, Math.Min(logCount, shift - ((JxlAnsConstants.AnsLogTableSize - logCount) >> 1))); |
|||
|
|||
// NOTE: The result may potentially be large, so prefer using a memory allocator
|
|||
public static IMemoryOwner<uint> CreateFlatHistogram(Configuration configuration, int length, int totalCount) |
|||
{ |
|||
Debug.Assert(length <= 0, "Length should be >= 0"); |
|||
Debug.Assert(length > totalCount, "Length should be <= totalCount"); |
|||
|
|||
int count = totalCount / length; |
|||
IMemoryOwner<uint> result = configuration.MemoryAllocator.Allocate<uint>(length); |
|||
Span<uint> resultSpan = result.Memory.Span; |
|||
uint unsignedCount = (uint)count; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
resultSpan[i] = unsignedCount; |
|||
} |
|||
|
|||
int remCounts = totalCount % length; |
|||
for (int i = 0; i < remCounts; i++) |
|||
{ |
|||
resultSpan[i]++; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public static JxlAnsSymbol Lookup(ReadOnlySpan<JxlAnsEntry> table, int value, int logEntrySize, int entrySizeMinus1) |
|||
{ |
|||
int i = value >> logEntrySize; |
|||
int pos = value & entrySizeMinus1; |
|||
|
|||
JxlAnsEntry entry = table[i]; |
|||
|
|||
int cutoff = entry.Cutoff; |
|||
int rightValue = entry.RightValue; |
|||
int freq0 = entry.Frequency0; |
|||
|
|||
bool greater = pos >= cutoff; |
|||
|
|||
int offsets1or0 = greater ? entry.Offsets1 : 0; |
|||
int freq1xorfreq0or0 = greater ? entry.Frequency1XorFrequency0 : 0; |
|||
|
|||
JxlAnsSymbol symbol = new() |
|||
{ |
|||
Value = greater ? rightValue : i, |
|||
Offset = offsets1or0 + pos, |
|||
Frequency = freq0 ^ freq1xorfreq0or0 |
|||
}; |
|||
|
|||
return symbol; |
|||
} |
|||
|
|||
public static bool InitAliasTable(Span<int> preDistribution, uint logRange, int logAlphaSize, Span<JxlAnsEntry> entries) |
|||
{ |
|||
int range = 1 << (int)logRange; |
|||
int tableSize = 1 << logAlphaSize; |
|||
|
|||
Debug.Assert(tableSize <= range, "table_size must be <= range"); |
|||
|
|||
int distributionPointer = preDistribution.Length - 1; |
|||
|
|||
while (distributionPointer >= 0 && preDistribution[distributionPointer] == 0) |
|||
{ |
|||
distributionPointer--; |
|||
} |
|||
|
|||
if (distributionPointer < 0) |
|||
{ |
|||
preDistribution[0] = range; |
|||
distributionPointer = 0; |
|||
} |
|||
|
|||
Span<int> distribution = preDistribution[..(distributionPointer + 1)]; |
|||
|
|||
if (distribution.Length > tableSize) |
|||
{ |
|||
Debug.Fail("Too many items in the distribution"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
int entrySize = range >> logAlphaSize; |
|||
int singleSymbol = -1; |
|||
int sum = 0; |
|||
|
|||
for (int sym = 0; sym < distribution.Length; sym++) |
|||
{ |
|||
int value = distribution[sym]; |
|||
sum += value; |
|||
|
|||
if (value == JxlAnsConstants.AnsTableSize) |
|||
{ |
|||
if (singleSymbol != -1) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
singleSymbol = sym; |
|||
} |
|||
} |
|||
|
|||
if (sum != range) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (singleSymbol != -1) |
|||
{ |
|||
byte sym = (byte)singleSymbol; |
|||
if (singleSymbol != sym) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (int i = 0; i < tableSize; i++) |
|||
{ |
|||
ref JxlAnsEntry jxlEntry = ref entries[i]; |
|||
|
|||
jxlEntry.RightValue = sym; |
|||
jxlEntry.Cutoff = 0; |
|||
jxlEntry.Offsets1 = (ushort)(entrySize * i); |
|||
jxlEntry.Frequency0 = 0; |
|||
jxlEntry.Frequency1XorFrequency0 = JxlAnsConstants.AnsTableSize; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
Span<uint> underfullPosn = stackalloc uint[distribution.Length]; |
|||
Span<uint> overfullPosn = stackalloc uint[distribution.Length]; |
|||
Span<uint> cutoffs = stackalloc uint[1 << logAlphaSize]; |
|||
|
|||
int underfullPointer = 0; |
|||
int overfullPointer = 0; |
|||
|
|||
for (int i = 0; i < distribution.Length; i++) |
|||
{ |
|||
uint currentCutoff = (uint)distribution[i]; |
|||
|
|||
cutoffs[i] = currentCutoff; |
|||
|
|||
if (currentCutoff > entrySize) |
|||
{ |
|||
overfullPosn[overfullPointer] = (uint)i; |
|||
overfullPointer++; |
|||
} |
|||
else if (currentCutoff < entrySize) |
|||
{ |
|||
underfullPosn[underfullPointer] = (uint)i; |
|||
underfullPointer++; |
|||
} |
|||
} |
|||
|
|||
for (int i = distribution.Length; i < tableSize; i++) |
|||
{ |
|||
cutoffs[i] = 0; |
|||
underfullPosn[underfullPointer] = (uint)i; |
|||
underfullPointer++; |
|||
} |
|||
|
|||
uint unsignedEntrySize = (uint)entrySize; |
|||
|
|||
while (overfullPointer >= 0) |
|||
{ |
|||
uint overfullIndex = overfullPosn[overfullPointer]; |
|||
overfullPointer--; |
|||
|
|||
if (underfullPointer <= -1) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
uint underfullIndex = underfullPosn[underfullPointer]; |
|||
underfullPointer--; |
|||
|
|||
int signedOverfullIndex = (int)overfullIndex; |
|||
int signedUnderfullIndex = (int)underfullIndex; |
|||
|
|||
uint underfullBy = unsignedEntrySize - cutoffs[signedUnderfullIndex]; |
|||
cutoffs[signedOverfullIndex] -= underfullBy; |
|||
|
|||
ref JxlAnsEntry currentEntry = ref entries[signedUnderfullIndex]; |
|||
|
|||
currentEntry.RightValue = unchecked((byte)overfullIndex); |
|||
currentEntry.Offsets1 = unchecked((ushort)cutoffs[signedOverfullIndex]); |
|||
|
|||
uint currentCutoff = cutoffs[signedOverfullIndex]; |
|||
|
|||
if (currentCutoff < entrySize) |
|||
{ |
|||
underfullPosn[underfullPointer] = overfullIndex; |
|||
underfullPointer++; |
|||
} |
|||
else if (currentCutoff > entrySize) |
|||
{ |
|||
overfullPosn[overfullPointer] = overfullIndex; |
|||
overfullPointer++; |
|||
} |
|||
} |
|||
|
|||
for (uint i = 0; i < tableSize; i++) |
|||
{ |
|||
uint currentCutoff = cutoffs[(int)i]; |
|||
ref JxlAnsEntry entry = ref entries[(int)i]; |
|||
|
|||
if (currentCutoff == entrySize) |
|||
{ |
|||
entry.RightValue = (byte)i; |
|||
entry.Offsets1 = 0; |
|||
entry.Cutoff = 0; |
|||
} |
|||
else |
|||
{ |
|||
entry.Offsets1 -= (ushort)currentCutoff; |
|||
entry.Cutoff = (byte)currentCutoff; |
|||
} |
|||
|
|||
int freq0 = i < distribution.Length ? distribution[(int)i] : 0; |
|||
int i1 = entry.RightValue; |
|||
int freq1 = i1 < distribution.Length ? distribution[i1] : 0; |
|||
|
|||
entry.Frequency0 = (ushort)freq0; |
|||
entry.Frequency1XorFrequency0 = (ushort)(freq1 ^ freq0); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
internal sealed class JxlAnsHybridUIntConfiguration : IJxlFields |
|||
{ |
|||
public JxlAnsHybridUIntConfiguration(uint splitExponent = 4, uint msbInToken = 2, uint lsbInToken = 0) |
|||
{ |
|||
this.SplitExponent = splitExponent; |
|||
this.SplitToken = 1u << (int)splitExponent; |
|||
this.MsbInToken = msbInToken; |
|||
this.LsbInToken = lsbInToken; |
|||
|
|||
Debug.Assert(splitExponent >= msbInToken + lsbInToken, "Split exponent should be < msbInToken + lsbInToken"); |
|||
} |
|||
|
|||
public uint SplitExponent { get; set; } |
|||
|
|||
public uint SplitToken { get; set; } |
|||
|
|||
public uint MsbInToken { get; set; } // Most significant bit
|
|||
|
|||
public uint LsbInToken { get; set; } // Least significant bit
|
|||
|
|||
public uint LsbMask => (1u << (int)this.LsbInToken) - 1; |
|||
|
|||
public void Encode(uint value, ref uint token, ref uint bitCount, ref uint bits) |
|||
{ |
|||
if (value < this.SplitToken) |
|||
{ |
|||
token = value; |
|||
bitCount = 0; |
|||
bits = 0; |
|||
} |
|||
else |
|||
{ |
|||
uint n = FloorLog2Nonzero(value); |
|||
uint m = value - (1u << (int)n); |
|||
|
|||
unchecked |
|||
{ |
|||
// The following expression is quite complex.
|
|||
// See https://github.com/libjxl/libjxl/blob/main/lib/jxl/dec_ans.h#L83C16-L86C47.
|
|||
token = this.SplitToken + |
|||
(uint)(((n - this.SplitExponent) << (int)(this.MsbInToken + this.LsbInToken)) + |
|||
((m >> (int)(n - this.MsbInToken)) << (int)this.LsbInToken) + |
|||
(m & ((1 << (int)this.LsbInToken) - 1))); |
|||
|
|||
bitCount = n - this.MsbInToken - this.LsbInToken; |
|||
bits = (value >> (int)this.LsbInToken) & ((1u << (int)bitCount) - 1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
internal sealed class JxlAnsLz77Parameters : IJxlFields |
|||
{ |
|||
public bool Enabled { get; set; } |
|||
|
|||
public uint MinimumSymbol { get; set; } |
|||
|
|||
public uint MinimumLength { get; set; } |
|||
|
|||
public JxlAnsHybridUIntConfiguration LengthUintConfig { get; set; } = new(0, 0, 0); |
|||
|
|||
public int NonserializedDistanceContext { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,256 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
internal static class JxlAnsReader |
|||
{ |
|||
// Prefer jagged arrays over multidimensional arrays
|
|||
// for performance. Collection expressions help represent
|
|||
// jagged arrays easily.
|
|||
private static readonly byte[][] HuffmanLookup = |
|||
[ |
|||
[3, 10], [7, 12], [3, 7], [4, 3], [3, 6], [3, 8], [3, 9], [4, 5], |
|||
[3, 10], [4, 4], [3, 7], [4, 1], [3, 6], [3, 8], [3, 9], [4, 2], |
|||
[3, 10], [5, 0], [3, 7], [4, 3], [3, 6], [3, 8], [3, 9], [4, 5], |
|||
[3, 10], [4, 4], [3, 7], [4, 1], [3, 6], [3, 8], [3, 9], [4, 2], |
|||
[3, 10], [6, 11], [3, 7], [4, 3], [3, 6], [3, 8], [3, 9], [4, 5], |
|||
[3, 10], [4, 4], [3, 7], [4, 1], [3, 6], [3, 8], [3, 9], [4, 2], |
|||
[3, 10], [5, 0], [3, 7], [4, 3], [3, 6], [3, 8], [3, 9], [4, 5], |
|||
[3, 10], [4, 4], [3, 7], [4, 1], [3, 6], [3, 8], [3, 9], [4, 2], |
|||
[3, 10], [7, 13], [3, 7], [4, 3], [3, 6], [3, 8], [3, 9], [4, 5], |
|||
[3, 10], [4, 4], [3, 7], [4, 1], [3, 6], [3, 8], [3, 9], [4, 2], |
|||
[3, 10], [5, 0], [3, 7], [4, 3], [3, 6], [3, 8], [3, 9], [4, 5], |
|||
[3, 10], [4, 4], [3, 7], [4, 1], [3, 6], [3, 8], [3, 9], [4, 2], |
|||
[3, 10], [6, 11], [3, 7], [4, 3], [3, 6], [3, 8], [3, 9], [4, 5], |
|||
[3, 10], [4, 4], [3, 7], [4, 1], [3, 6], [3, 8], [3, 9], [4, 2], |
|||
[3, 10], [5, 0], [3, 7], [4, 3], [3, 6], [3, 8], [3, 9], [4, 5], |
|||
[3, 10], [4, 4], [3, 7], [4, 1], [3, 6], [3, 8], [3, 9], [4, 2], |
|||
]; |
|||
|
|||
public static uint DecodeVariableLengthUint8(JxlBitReader reader) |
|||
{ |
|||
if (reader.ReadBoolean()) |
|||
{ |
|||
uint bitCount = reader.ReadBits32(3u); |
|||
|
|||
return bitCount == 0 |
|||
? 1u |
|||
: (reader.ReadBits32(bitCount) + (1u << (int)bitCount)); |
|||
} |
|||
|
|||
return 0u; |
|||
} |
|||
|
|||
public static uint DecodeVariableLengthUint16(JxlBitReader reader) |
|||
{ |
|||
if (reader.ReadBoolean()) |
|||
{ |
|||
uint bitCount = reader.ReadBits32(4u); |
|||
|
|||
return bitCount == 0 |
|||
? 1u |
|||
: (reader.ReadBits32(bitCount) + (1u << (int)bitCount)); |
|||
} |
|||
|
|||
return 0u; |
|||
} |
|||
|
|||
// NOTE: this method returns null on failure.
|
|||
// If the return value is a valid IMemoryOwner object,
|
|||
// then it succeeded.
|
|||
public static IMemoryOwner<uint>? ReadHistogram(Configuration configuration, int precisionBits, JxlBitReader reader) |
|||
{ |
|||
int range = 1 << precisionBits; |
|||
bool isSimpleCode = reader.ReadBoolean(); |
|||
|
|||
IMemoryOwner<uint> counts; |
|||
|
|||
if (isSimpleCode) |
|||
{ |
|||
Span<uint> symbols = stackalloc uint[2]; |
|||
symbols.Clear(); |
|||
|
|||
uint maxSymbol = 0u; |
|||
uint symCount = reader.ReadBits32(1u) + 1u; |
|||
for (uint i = 0; i < symCount; i++) |
|||
{ |
|||
uint symbol = DecodeVariableLengthUint8(reader); |
|||
if (symbol > maxSymbol) |
|||
{ |
|||
maxSymbol = symbol; |
|||
} |
|||
|
|||
symbols[(int)i] = symbol; |
|||
} |
|||
|
|||
// Up to 256 items
|
|||
counts = configuration.MemoryAllocator.Allocate<uint>((int)maxSymbol + 1); |
|||
Span<uint> countsSpan = counts.Memory.Span; |
|||
|
|||
if (symCount == 1) |
|||
{ |
|||
countsSpan[(int)symbols[0]] = (uint)range; |
|||
} |
|||
else |
|||
{ |
|||
if (symbols[0] == symbols[1]) |
|||
{ |
|||
Debug.Fail("Corrupt data"); |
|||
counts.Dispose(); |
|||
return null; |
|||
} |
|||
|
|||
countsSpan[(int)symbols[0]] = reader.ReadBits32((uint)precisionBits); |
|||
countsSpan[(int)symbols[1]] = (uint)range - countsSpan[(int)symbols[0]]; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
bool isFlat = reader.ReadBoolean(); |
|||
|
|||
if (isFlat) |
|||
{ |
|||
uint alphabetSize = DecodeVariableLengthUint8(reader) + 1u; |
|||
if (alphabetSize <= range) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
counts = JxlAnsHelper.CreateFlatHistogram(configuration, (int)alphabetSize, range); |
|||
return counts; |
|||
} |
|||
|
|||
int upperBoundLog = FloorLog2Nonzero(JxlAnsConstants.AnsLogTableSize + 1); |
|||
int log = 0; |
|||
|
|||
for (; log < upperBoundLog; log++) |
|||
{ |
|||
bool logIncrementBit = reader.ReadBoolean(); |
|||
|
|||
if (!logIncrementBit) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
|
|||
uint shift = (reader.ReadBits32((uint)log) | (1u << log)) - 1u; |
|||
|
|||
if (shift > JxlAnsConstants.AnsLogTableSize + 1) |
|||
{ |
|||
Debug.Fail("Invalid shift"); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
uint length = DecodeVariableLengthUint8(reader) + 3u; |
|||
|
|||
counts = configuration.MemoryAllocator.Allocate<uint>((int)length); |
|||
Span<uint> countsSpan = counts.Memory.Span; |
|||
|
|||
uint totalCount = 0; |
|||
|
|||
// The length variable can represent up to 258 elements,
|
|||
// so it'd be more beneficial to allocate on the stack
|
|||
// than pool an array.
|
|||
Span<int> logCounts = stackalloc int[(int)length]; |
|||
|
|||
// stackalloc doesn't zero-init, so clear just in case.
|
|||
logCounts.Clear(); |
|||
|
|||
int omitLog = -1; |
|||
int omitPos = -1; |
|||
|
|||
// See comments for logCounts definition
|
|||
Span<int> same = stackalloc int[(int)length]; |
|||
same.Clear(); |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
uint index = reader.PeekBits32(7); |
|||
reader.SkipBits32(HuffmanLookup[index][0]); |
|||
logCounts[i] = HuffmanLookup[index][1] - 1; |
|||
|
|||
if (logCounts[i] == JxlAnsConstants.AnsLogTableSize) |
|||
{ |
|||
uint rleLength = DecodeVariableLengthUint8(reader); |
|||
same[i] = (int)rleLength + 5; |
|||
i += (int)rleLength + 3; |
|||
continue; |
|||
} |
|||
|
|||
if (logCounts[i] > omitLog) |
|||
{ |
|||
omitLog = logCounts[i]; |
|||
omitPos = i; |
|||
} |
|||
} |
|||
|
|||
if (omitPos < 0) |
|||
{ |
|||
Debug.Fail("The histogram is corrupt or invalid."); |
|||
counts.Dispose(); |
|||
return null; |
|||
} |
|||
|
|||
if (omitPos + 1 < length && logCounts[omitPos + 1] == JxlAnsConstants.AnsLogTableSize) |
|||
{ |
|||
Debug.Fail("The histogram is corrupt or invalid."); |
|||
counts.Dispose(); |
|||
return null; |
|||
} |
|||
|
|||
int previous = 0; |
|||
int sameCount = 0; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
if (same[i] > 0) |
|||
{ |
|||
sameCount = same[i] - 1; |
|||
previous = i > 0 ? (int)countsSpan[i - 1] : 0; |
|||
} |
|||
|
|||
if (sameCount > 0) |
|||
{ |
|||
countsSpan[i] = (uint)previous; |
|||
sameCount--; |
|||
} |
|||
else |
|||
{ |
|||
int code = logCounts[i]; |
|||
|
|||
if (i == omitPos || code < 0) |
|||
{ |
|||
continue; |
|||
} |
|||
else if (shift == 0 || code == 0) |
|||
{ |
|||
countsSpan[i] = 1u << code; |
|||
} |
|||
else |
|||
{ |
|||
int bitCount = JxlAnsHelper.GetPopulationCountPrecision(code, (int)shift); |
|||
countsSpan[i] = (1u << code) + (reader.ReadBits32((uint)bitCount) << (code - bitCount)); |
|||
} |
|||
} |
|||
|
|||
totalCount += countsSpan[i]; |
|||
} |
|||
|
|||
countsSpan[omitPos] = (uint)range - totalCount; |
|||
|
|||
if (countsSpan[omitPos] <= 0) |
|||
{ |
|||
Debug.Fail("The histogram count is incorrect."); |
|||
counts.Dispose(); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
return counts; |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct JxlAnsSymbol(int value, int offset, int frequency) |
|||
{ |
|||
public int Value = value; |
|||
public int Offset = offset; |
|||
public int Frequency = frequency; |
|||
} |
|||
@ -0,0 +1,167 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers.Binary; |
|||
using System.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
/// <summary>
|
|||
/// Represents a bitstream reader.
|
|||
/// </summary>
|
|||
internal sealed class JxlBitReader(ReadOnlyMemory<byte> bytes) |
|||
{ |
|||
private ulong buffer; |
|||
private uint bufferRemainingBits; |
|||
private int pointer; |
|||
private bool endOfStream; |
|||
|
|||
/// <summary>
|
|||
/// Fetches a new buffer.
|
|||
/// </summary>
|
|||
private void RefillCore() |
|||
{ |
|||
ReadOnlySpan<byte> samplesSpan = bytes.Span; |
|||
|
|||
int remaining = samplesSpan.Length - this.pointer; |
|||
if (remaining <= 0) |
|||
{ |
|||
// we don't have any more data... mark an end of stream
|
|||
this.buffer = 0; |
|||
this.bufferRemainingBits = 0; |
|||
this.endOfStream = true; |
|||
return; |
|||
} |
|||
|
|||
if (remaining >= 8) |
|||
{ |
|||
this.buffer = BinaryPrimitives.ReadUInt64LittleEndian(samplesSpan[this.pointer..]); |
|||
this.bufferRemainingBits = 64u; |
|||
this.pointer += 8; |
|||
} |
|||
else |
|||
{ |
|||
ulong value = 0; |
|||
for (int i = 0; i < remaining; i++) |
|||
{ |
|||
value |= (ulong)samplesSpan[this.pointer + i] << (8 * i); |
|||
} |
|||
|
|||
this.buffer = value; |
|||
this.bufferRemainingBits = (uint)(remaining * 8); |
|||
this.pointer += remaining; |
|||
} |
|||
} |
|||
|
|||
private void MaybeRefill() |
|||
{ |
|||
if (this.bufferRemainingBits <= 0) |
|||
{ |
|||
this.RefillCore(); |
|||
} |
|||
} |
|||
|
|||
private ulong ReadBits64Core(uint n, bool peek = false) |
|||
{ |
|||
Debug.Assert(n <= 64, "Too many bits to pack into ulong"); |
|||
this.MaybeRefill(); |
|||
|
|||
if (this.endOfStream) |
|||
{ |
|||
JxlThrowHelper.ThrowEndOfStream(); |
|||
} |
|||
|
|||
if (n <= this.bufferRemainingBits) |
|||
{ |
|||
ulong result = this.buffer & ((1UL << (int)n) - 1); |
|||
|
|||
if (!peek) |
|||
{ |
|||
this.buffer >>= (int)n; |
|||
this.bufferRemainingBits -= n; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
else |
|||
{ |
|||
uint bitsFromCurrent = this.bufferRemainingBits; |
|||
ulong part = this.buffer & ((1UL << (int)bitsFromCurrent) - 1); |
|||
|
|||
this.buffer >>= (int)bitsFromCurrent; |
|||
this.bufferRemainingBits = 0; |
|||
|
|||
this.RefillCore(); |
|||
|
|||
uint bitsFromNext = n - bitsFromCurrent; |
|||
ulong nextPart = this.buffer & ((1UL << (int)bitsFromNext) - 1); |
|||
|
|||
if (!peek) |
|||
{ |
|||
this.buffer >>= (int)bitsFromNext; |
|||
this.bufferRemainingBits -= bitsFromNext; |
|||
} |
|||
|
|||
return part | (nextPart << (int)bitsFromCurrent); |
|||
} |
|||
} |
|||
|
|||
private uint ReadBits32Core(uint n, bool peek = false) |
|||
{ |
|||
Debug.Assert(n <= 32, "Too many bits to pack into uint"); |
|||
this.MaybeRefill(); |
|||
|
|||
if (this.endOfStream) |
|||
{ |
|||
JxlThrowHelper.ThrowEndOfStream(); |
|||
} |
|||
|
|||
if (n <= this.bufferRemainingBits) |
|||
{ |
|||
uint result = (uint)(this.buffer & ((1UL << (int)n) - 1)); |
|||
|
|||
if (!peek) |
|||
{ |
|||
this.buffer >>= (int)n; |
|||
this.bufferRemainingBits -= n; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
else |
|||
{ |
|||
uint bitsFromCurrent = this.bufferRemainingBits; |
|||
uint part = (uint)(this.buffer & ((1UL << (int)bitsFromCurrent) - 1)); |
|||
|
|||
this.buffer >>= (int)bitsFromCurrent; |
|||
this.bufferRemainingBits = 0; |
|||
|
|||
this.RefillCore(); |
|||
|
|||
uint bitsFromNext = n - bitsFromCurrent; |
|||
uint nextPart = (uint)(this.buffer & ((1UL << (int)bitsFromNext) - 1)); |
|||
|
|||
if (!peek) |
|||
{ |
|||
this.buffer >>= (int)bitsFromNext; |
|||
this.bufferRemainingBits -= bitsFromNext; |
|||
} |
|||
|
|||
return part | (nextPart << (int)bitsFromCurrent); |
|||
} |
|||
} |
|||
|
|||
public uint ReadBits32(uint bits) => this.ReadBits32Core(bits, peek: false); |
|||
|
|||
public uint PeekBits32(uint bits) => this.ReadBits32Core(bits, peek: true); |
|||
|
|||
public void SkipBits32(uint bits) => _ = this.ReadBits32(bits); |
|||
|
|||
public ulong ReadBits64(uint bits) => this.ReadBits64Core(bits, peek: false); |
|||
|
|||
public ulong PeekBits64(uint bits) => this.ReadBits64Core(bits, peek: true); |
|||
|
|||
public void SkipBits64(uint bits) => _ = this.ReadBits64(bits); |
|||
|
|||
public bool ReadBoolean() => this.ReadBits32Core(1, peek: false) == 1; |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
#pragma warning disable SA1649 // File name should match first type name
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl; |
|||
|
|||
[InlineArray(3)] |
|||
internal struct InlineArray3<T> |
|||
{ |
|||
private T first; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Used by JxlCustomTransformData
|
|||
/// </summary>
|
|||
[InlineArray(15)] |
|||
internal struct InlineArray15<T> |
|||
{ |
|||
private T first; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Used by JxlCustomTransformData
|
|||
/// </summary>
|
|||
[InlineArray(55)] |
|||
internal struct InlineArray55<T> |
|||
{ |
|||
private T first; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Used by JxlCustomTransformData
|
|||
/// </summary>
|
|||
[InlineArray(210)] |
|||
internal struct InlineArray210<T> |
|||
{ |
|||
private T first; |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl; |
|||
|
|||
internal static class JxlAspectRatioHelpers |
|||
{ |
|||
private static readonly SignedRational[] Ratios = |
|||
[ |
|||
new(1, 1), |
|||
new(12, 10), |
|||
new(4, 3), |
|||
new(3, 2), |
|||
new(16, 9), |
|||
new(5, 4), |
|||
new(2, 1) |
|||
]; |
|||
|
|||
public static SignedRational FixedAspectRatios(int ratio) => Ratios[ratio - 1]; |
|||
|
|||
public static int FindAspectRatio(int x, int y) |
|||
{ |
|||
for (int i = 0; i < 7; i++) |
|||
{ |
|||
if (x == MultiplyTruncate(Ratios[i], y)) |
|||
{ |
|||
return i; |
|||
} |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int MultiplyTruncate(SignedRational rational, int multiplicand) => (multiplicand * rational.Numerator) / rational.Denominator; |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl; |
|||
|
|||
internal class JxlFormat : IImageFormat |
|||
{ |
|||
public string Name => "JPEG XL"; |
|||
|
|||
public string DefaultMimeType => "image/jxl"; |
|||
|
|||
IEnumerable<string> IImageFormat.MimeTypes => new[] { "image/jxl" }; |
|||
|
|||
IEnumerable<string> IImageFormat.FileExtensions => new[] { "jxl" }; |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl; |
|||
|
|||
internal sealed class JxlFrameDimensions |
|||
{ |
|||
public const int BlockDimensions = 8; |
|||
public const int DctBlockSize = BlockDimensions * BlockDimensions; |
|||
public const int GroupDimensions = 256; |
|||
public const int GroupDimensionsInBlocks = GroupDimensions / BlockDimensions; |
|||
|
|||
public JxlFrameDimensions(int xSizePixel, int ySizePixel, int groupSizeShift, int maxHorizontalShift, int maxVerticalShift, bool modularMode, int upsampling) |
|||
{ |
|||
this.GroupDimension = (GroupDimensions >> 1) << groupSizeShift; |
|||
this.DcGroupDimension = this.GroupDimension * BlockDimensions; |
|||
this.XSizeUpsampled = xSizePixel; |
|||
this.YSizeUpsampled = ySizePixel; |
|||
this.XSize = DivCeil(xSizePixel, upsampling); |
|||
this.YSize = DivCeil(ySizePixel, upsampling); |
|||
this.XSizeBlocks = DivCeil(this.XSize, BlockDimensions << maxHorizontalShift) << maxHorizontalShift; |
|||
this.YSizeBlocks = DivCeil(this.YSize, BlockDimensions << maxVerticalShift) << maxVerticalShift; |
|||
this.XSizePadded = this.XSizeBlocks * BlockDimensions; |
|||
this.YSizePadded = this.YSizeBlocks * BlockDimensions; |
|||
|
|||
if (modularMode) |
|||
{ |
|||
this.XSizePadded = this.XSize; |
|||
this.YSizePadded = this.YSize; |
|||
} |
|||
|
|||
this.XSizeUpsampledPadded = this.XSizePadded * upsampling; |
|||
this.YSizeUpsampledPadded = this.YSizePadded * upsampling; |
|||
this.XSizeGroups = DivCeil(this.XSize, GroupDimensions); |
|||
this.YSizeGroups = DivCeil(this.YSize, GroupDimensions); |
|||
this.XSizeDcGroups = DivCeil(this.XSizeBlocks, GroupDimensions); |
|||
this.YSizeDcGroups = DivCeil(this.YSizeBlocks, GroupDimensions); |
|||
this.NumGroups = this.XSizeGroups * this.YSizeGroups; |
|||
this.NumDcGroups = this.XSizeDcGroups * this.YSizeDcGroups; |
|||
} |
|||
|
|||
public int XSize { get; set; } |
|||
|
|||
public int YSize { get; set; } |
|||
|
|||
public int XSizeUpsampled { get; set; } |
|||
|
|||
public int YSizeUpsampled { get; set; } |
|||
|
|||
public int XSizeUpsampledPadded { get; set; } |
|||
|
|||
public int YSizeUpsampledPadded { get; set; } |
|||
|
|||
public int XSizePadded { get; set; } |
|||
|
|||
public int YSizePadded { get; set; } |
|||
|
|||
public int XSizeBlocks { get; set; } |
|||
|
|||
public int YSizeBlocks { get; set; } |
|||
|
|||
public int XSizeGroups { get; set; } |
|||
|
|||
public int YSizeGroups { get; set; } |
|||
|
|||
public int XSizeDcGroups { get; set; } |
|||
|
|||
public int YSizeDcGroups { get; set; } |
|||
|
|||
public int NumGroups { get; set; } |
|||
|
|||
public int NumDcGroups { get; set; } |
|||
|
|||
public int GroupDimension { get; set; } |
|||
|
|||
public int DcGroupDimension { get; set; } |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int DivCeil(int x, int y) => x / y; |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl; |
|||
|
|||
internal static class JxlThrowHelper |
|||
{ |
|||
[DoesNotReturn] |
|||
public static void ThrowEndOfStream() => throw new EndOfStreamException(); |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a three-plane, 2D raster image of type <see cref="byte"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImage3B : JxlImage3<byte> |
|||
{ |
|||
public JxlImage3B() |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a three-plane, 2D raster image of type <see cref="float"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImage3F : JxlImage3<float> |
|||
{ |
|||
public JxlImage3F() |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a three-plane, 2D raster image of type <see cref="int"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImage3I : JxlImage3<int> |
|||
{ |
|||
public JxlImage3I() |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a three-plane, 2D raster image of type <see cref="short"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImage3S : JxlImage3<short> |
|||
{ |
|||
public JxlImage3S() |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a three-plane, 2D raster image of type <see cref="ushort"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImage3U : JxlImage3<ushort> |
|||
{ |
|||
public JxlImage3U() |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="byte"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageB : JxlPlane<byte> |
|||
{ |
|||
public JxlImageB() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageB(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageB(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
|
|||
public Memory<byte> GetRowBytesMemory(int y) |
|||
{ |
|||
Debug.Assert(y < this.YSize, "Attempted to access out-of-bounds Y coordinate"); |
|||
|
|||
Memory<byte> row = this.Bytes[(y * this.BytesPerRow)..]; |
|||
|
|||
return row; |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="float"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageF : JxlPlane<float> |
|||
{ |
|||
public JxlImageF() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageF(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageF(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="int"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageI : JxlPlane<int> |
|||
{ |
|||
public JxlImageI() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageI(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageI(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="short"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageS : JxlPlane<short> |
|||
{ |
|||
public JxlImageS() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageS(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageS(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="sbyte"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageSB : JxlPlane<sbyte> |
|||
{ |
|||
public JxlImageSB() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageSB(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageSB(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="ushort"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageU : JxlPlane<ushort> |
|||
{ |
|||
public JxlImageU() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageU(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageU(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory; |
|||
|
|||
// NOTE: Do not seal this class.
|
|||
internal class JxlImage3<T> |
|||
where T : unmanaged |
|||
{ |
|||
private const int PlaneCount = 3; |
|||
|
|||
private JxlPlane<T>[] planes = new JxlPlane<T>[3]; |
|||
|
|||
public JxlImage3() |
|||
{ |
|||
} |
|||
|
|||
public JxlImage3(JxlImage3<T> other) |
|||
{ |
|||
for (int i = 0; i < PlaneCount; i++) |
|||
{ |
|||
this.planes[i] = other.planes[i]; |
|||
} |
|||
} |
|||
|
|||
public int XSize => this.planes[0].XSize; |
|||
|
|||
public int YSize => this.planes[0].YSize; |
|||
|
|||
public int BytesPerRow => this.planes[0].BytesPerRow; |
|||
|
|||
public int PixelsPerRow => this.planes[0].PixelsPerRow; |
|||
|
|||
public Span<T> PlaneRow(int plane, int row) |
|||
{ |
|||
this.PlaneRowBoundsCheck(plane, row); |
|||
|
|||
int rowOffset = row * this.planes[0].BytesPerRow; |
|||
Span<T> rowSpan = MemoryMarshal.Cast<byte, T>(this.planes[plane].BytesSpan[rowOffset..]); |
|||
|
|||
return rowSpan; |
|||
} |
|||
|
|||
public JxlPlane<T> Plane(int index) => this.planes[index]; |
|||
|
|||
public void Swap(JxlImage3<T> other) |
|||
{ |
|||
for (int i = 0; i < PlaneCount; i++) |
|||
{ |
|||
other.planes[i].Swap(this.planes[i]); |
|||
} |
|||
} |
|||
|
|||
public static JxlImage3<T> Create(Configuration configuration, int xSize, int ySize) |
|||
{ |
|||
JxlPlane<T> plane0 = JxlPlane<T>.Create(configuration, xSize, ySize); |
|||
JxlPlane<T> plane1 = JxlPlane<T>.Create(configuration, xSize, ySize); |
|||
JxlPlane<T> plane2 = JxlPlane<T>.Create(configuration, xSize, ySize); |
|||
|
|||
return new JxlImage3<T>() |
|||
{ |
|||
planes = [plane0, plane1, plane2] |
|||
}; |
|||
} |
|||
|
|||
public bool ShrinkTo(int x, int y) |
|||
{ |
|||
for (int i = 0; i < PlaneCount; i++) |
|||
{ |
|||
if (!this.planes[i].ShrinkTo(x, y)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
[Conditional("DEBUG")] |
|||
private void PlaneRowBoundsCheck(int c, int y) => |
|||
Debug.Assert(c < PlaneCount && y < this.YSize, "The bounds check has failed"); |
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Diagnostics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory; |
|||
|
|||
// NOTE: Do not seal this type.
|
|||
internal class JxlPlaneBase : IDisposable |
|||
{ |
|||
private IMemoryOwner<byte>? bytes; |
|||
|
|||
public JxlPlaneBase(int xSize, int ySize, int sizeOfT) |
|||
{ |
|||
this.XSize = xSize; |
|||
this.YSize = ySize; |
|||
this.OriginalXSize = xSize; |
|||
this.OriginalYSize = ySize; |
|||
this.BytesPerRow = 0; |
|||
this.Size = sizeOfT; |
|||
} |
|||
|
|||
public JxlPlaneBase() |
|||
: this(0, 0, 0) |
|||
{ |
|||
} |
|||
|
|||
public int BytesPerRow { get; private set; } |
|||
|
|||
public int XSize { get; private set; } |
|||
|
|||
public int YSize { get; private set; } |
|||
|
|||
public Memory<byte> Bytes => |
|||
#if DEBUG
|
|||
this.bytes?.Memory ?? throw new InvalidOperationException("Bytes are missing"); |
|||
#else
|
|||
return this.bytes!.Memory; |
|||
#endif
|
|||
|
|||
public Span<byte> BytesSpan => this.Bytes.Span; |
|||
|
|||
protected int Size { get; set; } |
|||
|
|||
protected int OriginalXSize { get; set; } |
|||
|
|||
protected int OriginalYSize { get; set; } |
|||
|
|||
public bool Allocate(Configuration configuration, int prePadding) |
|||
{ |
|||
if (this.bytes != null || this.BytesPerRow != 0) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (this.XSize == 0 || this.YSize == 0) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
int totalBytes = unchecked(this.YSize * this.BytesPerRow); |
|||
|
|||
this.bytes = configuration.MemoryAllocator.Allocate<byte>(totalBytes + (prePadding * this.Size)); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool ShrinkTo(int x, int y) |
|||
{ |
|||
if (x <= this.OriginalXSize || y <= this.OriginalYSize) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
Debug.Assert(x <= this.OriginalXSize, "ShrinkTo cannot expand memory"); |
|||
Debug.Assert(y <= this.OriginalYSize, "ShrinkTo cannot expand memory"); |
|||
|
|||
this.XSize = x; |
|||
this.YSize = y; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
protected Span<T> GetRowBase<T>(int y) |
|||
where T : unmanaged |
|||
{ |
|||
Debug.Assert(y < this.YSize, "Attempted to access out-of-bounds Y coordinate"); |
|||
|
|||
Span<byte> row = this.Bytes.Span[(y * this.BytesPerRow)..]; |
|||
|
|||
return MemoryMarshal.Cast<byte, T>(row); |
|||
} |
|||
|
|||
protected void SetBytes(IMemoryOwner<byte> bytes) => this.bytes = bytes; |
|||
|
|||
public void Swap(JxlPlaneBase other) |
|||
{ |
|||
(this.XSize, other.XSize) = (other.XSize, this.XSize); |
|||
(this.YSize, other.YSize) = (other.YSize, this.YSize); |
|||
(this.OriginalXSize, other.OriginalXSize) = (other.OriginalXSize, this.OriginalXSize); |
|||
(this.OriginalYSize, other.OriginalYSize) = (other.OriginalYSize, this.OriginalYSize); |
|||
(this.BytesPerRow, other.BytesPerRow) = (other.BytesPerRow, this.BytesPerRow); |
|||
(this.bytes, other.bytes) = (other.bytes, this.bytes); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
this.bytes?.Dispose(); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory; |
|||
|
|||
// NOTE: Do not seal this class.
|
|||
internal class JxlPlane<T> : JxlPlaneBase |
|||
where T : unmanaged |
|||
{ |
|||
public JxlPlane() |
|||
{ |
|||
} |
|||
|
|||
public unsafe JxlPlane(int width, int height) |
|||
: base(width, height, sizeof(T)) |
|||
{ |
|||
} |
|||
|
|||
public unsafe int PixelsPerRow => this.BytesPerRow / sizeof(T); |
|||
|
|||
public static JxlPlane<T> Create(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
{ |
|||
JxlPlane<T> plane = new(xSize, ySize); |
|||
|
|||
bool allocated = plane.Allocate(configuration, prePadding); |
|||
|
|||
if (!allocated) |
|||
{ |
|||
throw new InvalidOperationException("Failed to allocate a JPEG XL plane"); |
|||
} |
|||
|
|||
return plane; |
|||
} |
|||
|
|||
public Span<T> GetRow(int y) => this.GetRowBase<T>(y); |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal sealed class JxlAnimationHeader : IJxlFields |
|||
{ |
|||
public int TpsNumerator { get; set; } |
|||
|
|||
public int TpsDenominator { get; set; } |
|||
|
|||
public int LoopCount { get; set; } |
|||
|
|||
public bool ContainsTimecodes { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal sealed class JxlBitDepth : IJxlFields |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether
|
|||
/// the original (uncompressed) samples are floating point or
|
|||
/// unsigned integer.
|
|||
/// </summary>
|
|||
public bool FloatingPointSample { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the bit depth of the original (uncompressed) image samples.
|
|||
/// Must be in the range [1, 32].
|
|||
/// </summary>
|
|||
public int BitsPerSample { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// <para>
|
|||
/// Gets or sets floating point exponent bits of the original (uncompressed) image samples,
|
|||
/// only used if <see cref="FloatingPointSample"/> is <see langword="true"/>.
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// If used, the samples are floating point with:
|
|||
/// <list type="bullet">
|
|||
/// <item>1 sign bit</item>
|
|||
/// <item><see cref="ExponentBitsPerSample"/> exponent bits</item>
|
|||
/// <item>(<see cref="BitsPerSample"/> - <see cref="ExponentBitsPerSample"/> - 1) mantissa bits</item>
|
|||
/// </list>
|
|||
/// If used, <see cref="ExponentBitsPerSample"/> must be in the range
|
|||
/// [2, 8] and amount of mantissa bits must be in the range [2, 23].
|
|||
/// </para>
|
|||
/// </summary>
|
|||
public int ExponentBitsPerSample { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal sealed class JxlCodecMetadata |
|||
{ |
|||
public JxlImageMetadata? ImageMetadata { get; set; } |
|||
|
|||
public SizeHeader Size { get; set; } |
|||
|
|||
public JxlCustomTransformData? CustomTransformData { get; set; } |
|||
|
|||
public int XSize => this.Size.XSize; |
|||
|
|||
public int YSize => this.Size.YSize; |
|||
|
|||
public int GetOrientedPreviewXSize(bool keepOrientation) |
|||
{ |
|||
if (this.ImageMetadata!.Orientation > 4 && !keepOrientation) |
|||
{ |
|||
return this.ImageMetadata.PreviewSize.YSize; |
|||
} |
|||
|
|||
return this.ImageMetadata.PreviewSize.XSize; |
|||
} |
|||
|
|||
public int GetOrientedPreviewYSize(bool keepOrientation) |
|||
{ |
|||
if (this.ImageMetadata!.Orientation > 4 && !keepOrientation) |
|||
{ |
|||
return this.ImageMetadata.PreviewSize.XSize; |
|||
} |
|||
|
|||
return this.ImageMetadata.PreviewSize.YSize; |
|||
} |
|||
|
|||
public int GetOrientedXSize(bool keepOrientation) |
|||
{ |
|||
if (this.ImageMetadata!.Orientation > 4 && !keepOrientation) |
|||
{ |
|||
return this.YSize; |
|||
} |
|||
|
|||
return this.XSize; |
|||
} |
|||
|
|||
public int GetOrientedYSize(bool keepOrientation) |
|||
{ |
|||
if (this.ImageMetadata!.Orientation > 4 && !keepOrientation) |
|||
{ |
|||
return this.XSize; |
|||
} |
|||
|
|||
return this.YSize; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal sealed class JxlCustomTransformData : IJxlFields |
|||
{ |
|||
public bool NonserializedXybEncoded { get; set; } |
|||
|
|||
public bool AllDefault { get; set; } |
|||
|
|||
public JxlOpsinInverseMatrix? OpsinInverseMatrix { get; set; } |
|||
|
|||
public int CustomWeightsMask { get; set; } |
|||
|
|||
public InlineArray15<float> Upsampling2Weights { get; set; } |
|||
|
|||
public InlineArray55<float> Upsampling4Weights { get; set; } |
|||
|
|||
public InlineArray210<float> Upsampling8Weights { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal enum JxlExifOrientation : byte |
|||
{ |
|||
Identity = 1, |
|||
FlipHorizontal = 2, |
|||
Rotate180 = 3, |
|||
FlipVertical = 4, |
|||
Transponse = 5, |
|||
Rotate90 = 6, |
|||
AntiTranspose = 7, |
|||
Rotate270 = 8 |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal enum JxlExtraChannel : byte |
|||
{ |
|||
Alpha, |
|||
Depth, |
|||
SpotColor, |
|||
SelectionMask, |
|||
Black, |
|||
Cfa, |
|||
Thermal, |
|||
Reserved0, |
|||
Reserved1, |
|||
Reserved2, |
|||
Reserved3, |
|||
Reserved4, |
|||
Reserved5, |
|||
Reserved6, |
|||
Reserved7, |
|||
Unknown, |
|||
Optional |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal sealed class JxlExtraChannelInfo : IJxlFields |
|||
{ |
|||
public bool AllDefault { get; set; } |
|||
|
|||
public JxlExtraChannel Type { get; set; } |
|||
|
|||
public JxlBitDepth? BitDepth { get; set; } |
|||
|
|||
public int DimensionShift { get; set; } |
|||
|
|||
public string? Name { get; set; } |
|||
|
|||
public bool AlphaAssociated { get; set; } |
|||
|
|||
public InlineArray4<float> SpotColor { get; set; } |
|||
|
|||
public int CfaChannel { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics; |
|||
using SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal sealed class JxlImageMetadata : IJxlFields |
|||
{ |
|||
public bool AllDefault { get; set; } |
|||
|
|||
public JxlBitDepth? BitDepth { get; set; } |
|||
|
|||
public bool Modular16BitBufferSufficient { get; set; } // Otherwise, 32 is
|
|||
|
|||
public bool XybEncoded { get; set; } |
|||
|
|||
public JxlColorEncoding? ColorEncoding { get; set; } |
|||
|
|||
public int Orientation { get; set; } = 1; |
|||
|
|||
public bool HavePreview { get; set; } |
|||
|
|||
public bool HaveAnimation { get; set; } |
|||
|
|||
public bool HaveIntrinsicSize { get; set; } |
|||
|
|||
public JxlSizeHeader IntrinsicSize { get; set; } |
|||
|
|||
public JxlToneMapping? ToneMapping { get; set; } |
|||
|
|||
public int ExtraChannelCount { get; set; } |
|||
|
|||
public List<JxlExtraChannelInfo> ExtraChannels { get; set; } = []; |
|||
|
|||
public JxlPreviewHeader PreviewSize { get; set; } |
|||
|
|||
public JxlAnimationHeader Animation { get; set; } |
|||
|
|||
public long Extensions { get; set; } |
|||
|
|||
public bool NonserializedOnlyParseBasicInfos { get; set; } |
|||
|
|||
public float IntensityTarget |
|||
{ |
|||
get |
|||
{ |
|||
float intensityTarget = this.ToneMapping?.IntensityTarget ?? 0f; |
|||
|
|||
Debug.Assert(intensityTarget != 0f, "Intensity target should be present"); |
|||
|
|||
return intensityTarget; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (this.ToneMapping != null) |
|||
{ |
|||
this.ToneMapping.IntensityTarget = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public int AlphaBits |
|||
{ |
|||
get |
|||
{ |
|||
JxlExtraChannelInfo? ec = this.FindExtraChannel(JxlExtraChannel.Alpha); |
|||
|
|||
if (ec == null) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
return ec.BitDepth?.BitsPerSample ?? 0; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
} |
|||
} |
|||
|
|||
public bool HasAlpha => this.AlphaBits != 0; |
|||
|
|||
public JxlExtraChannelInfo? FindExtraChannel(JxlExtraChannel type) |
|||
=> this.ExtraChannels.FirstOrDefault(eci => eci.Type == type); |
|||
|
|||
public JxlExifOrientation GetExifOrientation() => (JxlExifOrientation)this.Orientation; |
|||
|
|||
public void SetFloat16Samples() |
|||
{ |
|||
if (this.BitDepth != null) |
|||
{ |
|||
this.BitDepth.BitsPerSample = 16; |
|||
this.BitDepth.ExponentBitsPerSample = 5; |
|||
this.BitDepth.FloatingPointSample = true; |
|||
} |
|||
|
|||
this.Modular16BitBufferSufficient = false; |
|||
} |
|||
|
|||
public void SetFloat32Samples() |
|||
{ |
|||
if (this.BitDepth != null) |
|||
{ |
|||
this.BitDepth.BitsPerSample = 32; |
|||
this.BitDepth.ExponentBitsPerSample = 8; |
|||
this.BitDepth.FloatingPointSample = true; |
|||
} |
|||
|
|||
this.Modular16BitBufferSufficient = false; |
|||
} |
|||
|
|||
public void SetUIntSamples(int bits) |
|||
{ |
|||
if (this.BitDepth != null) |
|||
{ |
|||
this.BitDepth.BitsPerSample = bits; |
|||
this.BitDepth.ExponentBitsPerSample = 0; |
|||
this.BitDepth.FloatingPointSample = false; |
|||
} |
|||
|
|||
this.Modular16BitBufferSufficient = bits <= 12; |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal sealed class JxlOpsinInverseMatrix : IJxlFields |
|||
{ |
|||
public bool AllDefault { get; set; } |
|||
|
|||
public JxlMatrix3x3 InverseMatrix { get; set; } |
|||
|
|||
public InlineArray3<float> OpsinBiases { get; set; } |
|||
|
|||
public InlineArray4<float> QuantBiases { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal sealed class JxlPreviewHeader : IJxlFields |
|||
{ |
|||
private bool div8; |
|||
private int ySizeDiv8; |
|||
private int ySize; |
|||
private int ratio; |
|||
private int xSizeDiv8; |
|||
private int xSize; |
|||
|
|||
public int YSize => this.div8 ? (this.ySizeDiv8 * 8) : this.ySize; |
|||
|
|||
public int XSize |
|||
{ |
|||
get |
|||
{ |
|||
if (this.ratio != 0) |
|||
{ |
|||
SignedRational signedRational = JxlAspectRatioHelpers.FixedAspectRatios(this.ratio); |
|||
|
|||
return JxlAspectRatioHelpers.MultiplyTruncate(signedRational, this.YSize); |
|||
} |
|||
|
|||
return this.div8 ? (this.xSizeDiv8 * 8) : this.xSize; |
|||
} |
|||
} |
|||
|
|||
public void Set(int x, int y) |
|||
{ |
|||
if (x == 0 || y == 0) |
|||
{ |
|||
throw new ArgumentException("Empty preview"); |
|||
} |
|||
|
|||
this.div8 = ((x % JxlFrameDimensions.BlockDimensions) | (y % JxlFrameDimensions.BlockDimensions)) == 0; |
|||
|
|||
if (this.div8) |
|||
{ |
|||
this.ySizeDiv8 = y / 8; |
|||
} |
|||
else |
|||
{ |
|||
this.ySize = y; |
|||
} |
|||
|
|||
this.ratio = JxlAspectRatioHelpers.FindAspectRatio(x, y); |
|||
|
|||
if (this.ratio == 0) |
|||
{ |
|||
if (this.div8) |
|||
{ |
|||
this.xSizeDiv8 = x / 8; |
|||
} |
|||
else |
|||
{ |
|||
this.xSize = x; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal sealed class JxlSizeHeader : IJxlFields |
|||
{ |
|||
private bool isSmall; |
|||
private int ySizeDiv8Minus1; |
|||
private int ySize; |
|||
private int ratio; |
|||
private int xSizeDiv8Minus1; |
|||
private int xSize; |
|||
|
|||
public int YSize => this.isSmall ? ((this.ySizeDiv8Minus1 + 1) * 8) : this.ySize; |
|||
|
|||
public int XSize |
|||
{ |
|||
get |
|||
{ |
|||
if (this.ratio != 0) |
|||
{ |
|||
SignedRational aspectRatio = JxlAspectRatioHelpers.FixedAspectRatios(this.ratio); |
|||
|
|||
return JxlAspectRatioHelpers.MultiplyTruncate(aspectRatio, this.YSize); |
|||
} |
|||
|
|||
return this.isSmall ? ((this.xSizeDiv8Minus1 + 1) * 8) : this.xSize; |
|||
} |
|||
} |
|||
|
|||
public void Set(int x, int y) |
|||
{ |
|||
if (x > int.MaxValue || y > int.MaxValue) |
|||
{ |
|||
throw new ArgumentException("Image too large"); |
|||
} |
|||
|
|||
if (x == 0 || y == 0) |
|||
{ |
|||
throw new ArgumentException("Empty image"); |
|||
} |
|||
|
|||
this.ratio = JxlAspectRatioHelpers.FindAspectRatio(x, y); |
|||
this.isSmall = y < 256 && (y % JxlFrameDimensions.BlockDimensions) == 0 |
|||
&& (this.ratio != 0 || (x <= 256 && (x % JxlFrameDimensions.BlockDimensions) == 0)); |
|||
|
|||
if (this.isSmall) |
|||
{ |
|||
this.ySizeDiv8Minus1 = (y / 8) - 1; |
|||
} |
|||
else |
|||
{ |
|||
this.ySize = y; |
|||
} |
|||
|
|||
if (this.ratio == 0) |
|||
{ |
|||
if (this.isSmall) |
|||
{ |
|||
this.xSizeDiv8Minus1 = (x / 8) - 1; |
|||
} |
|||
else |
|||
{ |
|||
this.xSize = x; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
internal sealed class JxlToneMapping : IJxlFields |
|||
{ |
|||
public bool AllDefault { get; set; } |
|||
|
|||
public float IntensityTarget { get; set; } |
|||
|
|||
public float LowerBoundIntensityLevel { get; set; } |
|||
|
|||
public bool RelativeToMaxDisplay { get; set; } |
|||
|
|||
public float LinearBelow { get; set;} |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
|
|||
internal sealed class JxlAlphaBlendingInputLayer |
|||
{ |
|||
public ReadOnlyMemory<float> R { get; set; } |
|||
|
|||
public ReadOnlyMemory<float> G { get; set; } |
|||
|
|||
public ReadOnlyMemory<float> B { get; set; } |
|||
|
|||
public ReadOnlyMemory<float> A { get; set; } |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
|
|||
internal sealed class JxlAlphaBlendingOutput |
|||
{ |
|||
public Memory<float> R { get; set; } |
|||
|
|||
public Memory<float> G { get; set; } |
|||
|
|||
public Memory<float> B { get; set; } |
|||
|
|||
public Memory<float> A { get; set; } |
|||
} |
|||
@ -0,0 +1,192 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
|
|||
internal sealed class JxlAlphaHelper |
|||
{ |
|||
// TODO: SIMD support
|
|||
private const float SmallAlpha = 1f / (1 << 26); |
|||
|
|||
// Force x to stay within the range of 0 through 1
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static float Clamp(float x) => Math.Clamp(x, 0f, 1f); |
|||
|
|||
public static void PerformAlphaBlending( |
|||
JxlAlphaBlendingInputLayer background, |
|||
JxlAlphaBlendingInputLayer foreground, |
|||
JxlAlphaBlendingOutput output, |
|||
int pixelCount, |
|||
bool alphaIsPremultiplied, |
|||
bool clamp) |
|||
{ |
|||
// Store all channels from all parameters into spans, because
|
|||
// creating a Span<T> from Memory<T> is expensive, especially
|
|||
// in a loop.
|
|||
ReadOnlySpan<float> fgR = foreground.R.Span; |
|||
ReadOnlySpan<float> fgG = foreground.G.Span; |
|||
ReadOnlySpan<float> fgB = foreground.B.Span; |
|||
ReadOnlySpan<float> fgA = foreground.A.Span; |
|||
|
|||
ReadOnlySpan<float> bgR = background.R.Span; |
|||
ReadOnlySpan<float> bgG = background.G.Span; |
|||
ReadOnlySpan<float> bgB = background.B.Span; |
|||
ReadOnlySpan<float> bgA = background.A.Span; |
|||
|
|||
Span<float> outR = output.R.Span; |
|||
Span<float> outG = output.G.Span; |
|||
Span<float> outB = output.B.Span; |
|||
Span<float> outA = output.A.Span; |
|||
|
|||
if (alphaIsPremultiplied) |
|||
{ |
|||
for (int x = 0; x < pixelCount; x++) |
|||
{ |
|||
float fga = clamp ? Clamp(fgA[x]) : fgA[x]; |
|||
outR[x] = fgR[x] + (bgR[x] * (1f - fga)); |
|||
outG[x] = fgG[x] + (bgG[x] * (1f - fga)); |
|||
outB[x] = fgB[x] + (bgB[x] * (1f - fga)); |
|||
outA[x] = 1f - ((1f - fga) * (1f - bgA[x])); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int x = 0; x < pixelCount; x++) |
|||
{ |
|||
float fga = clamp ? Clamp(fgA[x]) : fgA[x]; |
|||
float newA = 1f - ((1f - fga) * (1f - bgA[x])); |
|||
float rnewA = newA > 0 ? 1f / newA : 0f; |
|||
outR[x] = ((fgR[x] * fga) + (bgR[x] * bgA[x] * (1f - fga))) * rnewA; |
|||
outG[x] = ((fgG[x] * fga) + (bgG[x] * bgA[x] * (1f - fga))) * rnewA; |
|||
outB[x] = ((fgB[x] * fga) + (bgB[x] * bgA[x] * (1f - fga))) * rnewA; |
|||
outA[x] = newA; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void PerformAlphaBlending( |
|||
ReadOnlySpan<float> bg, |
|||
ReadOnlySpan<float> bga, |
|||
ReadOnlySpan<float> fg, |
|||
ReadOnlySpan<float> fga, |
|||
Span<float> output, |
|||
int pixelCount, |
|||
bool alphaIsPremultiplied, |
|||
bool clamp) |
|||
{ |
|||
if (bg == bga && fg == fga) |
|||
{ |
|||
for (int x = 0; x < pixelCount; x++) |
|||
{ |
|||
float fa = clamp ? Clamp(fga[x]) : fga[x]; |
|||
output[x] = 1f - ((1f - fa) * (1f - bga[x])); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (alphaIsPremultiplied) |
|||
{ |
|||
for (int x = 0; x < pixelCount; x++) |
|||
{ |
|||
float fa = clamp ? Clamp(fga[x]) : fga[x]; |
|||
output[x] = fg[x] + (bg[x] * (1f - fa)); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int x = 0; x < pixelCount; x++) |
|||
{ |
|||
float fa = clamp ? Clamp(fga[x]) : fga[x]; |
|||
float new_a = 1f - ((1f - fa) * (1f - bga[x])); |
|||
float rnew_a = new_a > 0 ? 1f / new_a : 0f; |
|||
output[x] = ((fg[x] * fa) + (bg[x] * bga[x] * (1f - fa))) * rnew_a; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void PerformAlphaWeightedAdd( |
|||
ReadOnlySpan<float> bg, |
|||
ReadOnlySpan<float> fg, |
|||
ReadOnlySpan<float> fga, |
|||
Span<float> output, |
|||
int pixelCount, |
|||
bool clamp) |
|||
{ |
|||
if (fg == fga) |
|||
{ |
|||
bg[pixelCount..].CopyTo(output); |
|||
} |
|||
else if (clamp) |
|||
{ |
|||
for (int x = 0; x < pixelCount; x++) |
|||
{ |
|||
output[x] = bg[x] + (fg[x] * Clamp(fga[x])); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int x = 0; x < pixelCount; ++x) |
|||
{ |
|||
output[x] = bg[x] + (fg[x] * fga[x]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void PerformMultiplyBlending( |
|||
ReadOnlySpan<float> bg, |
|||
ReadOnlySpan<float> fg, |
|||
Span<float> output, |
|||
int pixelCount, |
|||
bool clamp) |
|||
{ |
|||
if (clamp) |
|||
{ |
|||
for (int x = 0; x < pixelCount; x++) |
|||
{ |
|||
output[x] = bg[x] * Clamp(fg[x]); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int x = 0; x < pixelCount; x++) |
|||
{ |
|||
output[x] = bg[x] * fg[x]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void PremultiplyAlpha( |
|||
Span<float> r, |
|||
Span<float> g, |
|||
Span<float> b, |
|||
ReadOnlySpan<float> a, |
|||
int pixelCount) |
|||
{ |
|||
for (int x = 0; x < pixelCount; x++) |
|||
{ |
|||
float multiplier = Math.Max(SmallAlpha, a[x]); |
|||
r[x] *= multiplier; |
|||
g[x] *= multiplier; |
|||
b[x] *= multiplier; |
|||
} |
|||
} |
|||
|
|||
public static void UnpremultiplyAlpha( |
|||
Span<float> r, |
|||
Span<float> g, |
|||
Span<float> b, |
|||
ReadOnlySpan<float> a, |
|||
int pixelCount) |
|||
{ |
|||
for (int x = 0; x < pixelCount; x++) |
|||
{ |
|||
float multiplier = 1f / Math.Max(SmallAlpha, a[x]); |
|||
r[x] *= multiplier; |
|||
g[x] *= multiplier; |
|||
b[x] *= multiplier; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,106 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
|
|||
internal static class JxlLehmerCode |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int ValueOfLowest1Bit(int n) => n & -n; |
|||
|
|||
public static bool ComputeLehmerCode(ReadOnlySpan<int> permutation, Span<uint> temp, int n, Span<uint> code) |
|||
{ |
|||
temp[(n + 1)..].Clear(); |
|||
|
|||
for (int idx = 0; idx < n; idx++) |
|||
{ |
|||
int s = permutation[idx]; |
|||
|
|||
uint penalty = 0u; |
|||
uint i = (uint)s + 1u; |
|||
|
|||
while (i != 0u) |
|||
{ |
|||
penalty += temp[(int)i]; |
|||
i &= i - 1u; // Clear lowest bit
|
|||
} |
|||
|
|||
if (s < penalty) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
code[idx] = (uint)s - penalty; |
|||
i = (uint)s + 1u; |
|||
|
|||
while (i < n + 1u) |
|||
{ |
|||
temp[(int)i]++; |
|||
i += (uint)ValueOfLowest1Bit((int)i); |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public static bool DecodeLehmerCode(ReadOnlySpan<uint> code, Span<uint> temp, int n, Span<int> permutation) |
|||
{ |
|||
if (n == 0) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
int log2n = CeilLog2Nonzero(n); |
|||
int paddedN = 1 << log2n; |
|||
|
|||
for (int i = 0; i < paddedN; i++) |
|||
{ |
|||
int i1 = i + 1; |
|||
temp[i] = (uint)ValueOfLowest1Bit(i1); |
|||
} |
|||
|
|||
for (int i = 0; i < n; i++) |
|||
{ |
|||
if (code[i] + i >= n) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
uint rank = code[i] + 1; |
|||
|
|||
int bit = paddedN; |
|||
int next = 0; |
|||
|
|||
for (int b = 0; b <= log2n; b++) |
|||
{ |
|||
int cand = next + bit; |
|||
|
|||
if (cand < 1) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
bit >>= 1; |
|||
|
|||
if (temp[cand - 1] < rank) |
|||
{ |
|||
next = cand; |
|||
rank -= temp[cand - 1]; |
|||
} |
|||
} |
|||
|
|||
permutation[i] = next; |
|||
|
|||
next++; |
|||
while (next <= paddedN) |
|||
{ |
|||
temp[next - 1]--; |
|||
next += ValueOfLowest1Bit(next); |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
|
|||
internal static class JxlNoiseHelper |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static JxlNoiseIndexAndFraction IndexAndFraction(float x) |
|||
{ |
|||
const int scaleNumerator = JxlNoiseParameters.NoisePoints - 2; |
|||
const float scale = scaleNumerator / 1.0f; |
|||
|
|||
float scaledX = MathF.Max(0f, x * scale); |
|||
float floorX = MathF.Floor(scaledX); |
|||
float fractionalX = scaledX - floorX; |
|||
|
|||
if (scaledX >= scaleNumerator + 1) |
|||
{ |
|||
floorX = scaleNumerator; |
|||
fractionalX = 1f; |
|||
} |
|||
|
|||
return new((int)floorX, fractionalX); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct JxlNoiseIndexAndFraction(int index, float fraction) |
|||
{ |
|||
public int Index = index; |
|||
|
|||
public float Fraction = fraction; |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct JxlNoiseLevel(float noiseLevel, float intensity) |
|||
{ |
|||
public float NoiseLevel = noiseLevel; |
|||
|
|||
public float Intensity = intensity; |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
|
|||
internal sealed class JxlNoiseParameters |
|||
{ |
|||
public const int NoisePoints = 8; |
|||
|
|||
public float[] Lookup { get; set; } = new float[NoisePoints]; |
|||
|
|||
public bool ContainsAny => this.Lookup.Any(x => MathF.Abs(x) > 1e-3f); |
|||
|
|||
public void Clear() => Array.Fill(this.Lookup, 0f); |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
|
|||
internal sealed class JxlXorShift |
|||
{ |
|||
private readonly ulong[] s0 = new ulong[8]; |
|||
private readonly ulong[] s1 = new ulong[8]; |
|||
|
|||
public void XorShift128Plus(ulong seed) |
|||
{ |
|||
this.s0[0] = SplitMix64(seed + 0x9E3779B97F4A7C15L); |
|||
this.s1[0] = SplitMix64(this.s0[0]); |
|||
for (int i = 1; i < 8; ++i) |
|||
{ |
|||
this.s0[i] = SplitMix64(this.s1[i - 1]); |
|||
this.s1[i] = SplitMix64(this.s0[i]); |
|||
} |
|||
} |
|||
|
|||
public void XorShift128Plus(uint seed1, uint seed2, uint seed3, uint seed4) |
|||
{ |
|||
this.s0[0] = SplitMix64((((ulong)seed1 << 32) + seed2) + 0x9E3779B97F4A7C15uL); |
|||
this.s1[0] = SplitMix64((((ulong)seed3 << 32) + seed4) + 0x9E3779B97F4A7C15uL); |
|||
for (int i = 1; i < 8; ++i) |
|||
{ |
|||
this.s0[i] = SplitMix64(this.s0[i - 1]); |
|||
this.s1[i] = SplitMix64(this.s1[i - 1]); |
|||
} |
|||
} |
|||
|
|||
// TODO: SIMD
|
|||
public void Fill(Span<ulong> randomBits) |
|||
{ |
|||
for (int i = 0; i < 8; ++i) |
|||
{ |
|||
ulong s1 = this.s0[i]; |
|||
ulong s0 = this.s1[i]; |
|||
ulong bits = s1 + s0; |
|||
this.s0[i] = s0; |
|||
s1 ^= s1 << 23; |
|||
randomBits[i] = bits; |
|||
s1 ^= s0 ^ (s1 >> 18) ^ (s0 >> 5); |
|||
this.s1[i] = s1; |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static ulong SplitMix64(ulong z) |
|||
{ |
|||
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9uL; |
|||
z = (z ^ (z >> 27)) * 0x94D049BB133111EBuL; |
|||
return z ^ (z >> 31); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Splines; |
|||
|
|||
/// <summary>
|
|||
/// A simple pair of first and second 32-bit signed integers
|
|||
/// that represent a single control point.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct JxlControlPoint(int first, int second) |
|||
{ |
|||
public int First = first; |
|||
public int Second = second; |
|||
} |
|||
@ -0,0 +1,343 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Diagnostics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Splines; |
|||
|
|||
internal sealed class JxlQuantizedSpline : IDisposable |
|||
{ |
|||
private IMemoryOwner<JxlControlPoint>? memoryOwner; |
|||
|
|||
public JxlQuantizedSpline() |
|||
{ |
|||
for (int i = 0; i < 3; i++) |
|||
{ |
|||
this.ColorDct[i] = new int[32]; |
|||
} |
|||
} |
|||
|
|||
private static ReadOnlySpan<int> Loops => [1, 0, 2]; |
|||
|
|||
private static ReadOnlySpan<float> ChannelWeight => [0.0042f, 0.075f, 0.07f, .3333f]; |
|||
|
|||
public Memory<JxlControlPoint> ControlPoints { get; set; } |
|||
|
|||
// NOTE: Do not use Configuration.MemoryAllocator.Allocate2D. This is
|
|||
// a 3x32 array, and renting memory introduces too much overhead for
|
|||
// 384 bytes of memory.
|
|||
// Additionally, prefer jagged arrays instead of multidimensional arrays
|
|||
// for performance.
|
|||
public int[][] ColorDct { get; set; } = new int[3][]; |
|||
|
|||
public int[] SigmaDct { get; set; } = new int[32]; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
this.memoryOwner?.Dispose(); |
|||
this.ControlPoints = Memory<JxlControlPoint>.Empty; |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
|
|||
public void ReserveControlPoints(Configuration configuration, int n) |
|||
{ |
|||
this.memoryOwner = configuration.MemoryAllocator.Allocate<JxlControlPoint>(n); |
|||
|
|||
this.ControlPoints = this.memoryOwner.Memory; |
|||
} |
|||
|
|||
public static JxlQuantizedSpline Create(Configuration configuration, JxlSpline original, int quantizationAdjustment, float yToX, float yToB) |
|||
{ |
|||
JxlQuantizedSpline spline = new(); |
|||
|
|||
spline.ReserveControlPoints(configuration, original.ControlPoints.Count - 1); |
|||
|
|||
PointF startingPoint = original.ControlPoints.First(); |
|||
int previousX = (int)MathF.Round(startingPoint.X); |
|||
int previousY = (int)MathF.Round(startingPoint.Y); |
|||
int previousDx = 0; // D stands for delta
|
|||
int previousDy = 0; // D stands for delta
|
|||
|
|||
int length = original.ControlPoints.Length; |
|||
IMemoryOwner<JxlControlPoint> newControls = configuration.MemoryAllocator.Allocate<JxlControlPoint>(length); |
|||
Span<JxlControlPoint> controlsSpan = newControls.Memory.Span; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
PointF controlPoint = original.ControlPoints[i]; |
|||
|
|||
int newX = (int)MathF.Round(controlPoint.X); |
|||
int newY = (int)MathF.Round(controlPoint.Y); |
|||
int newDx = newX - previousX; // D stands for delta
|
|||
int newDy = newY - previousY; // D stands for delta
|
|||
|
|||
controlsSpan[i] = new(newDx - previousDx, newDy - previousDy); |
|||
|
|||
previousDx = newDx; |
|||
previousDy = newDy; |
|||
previousX = newX; |
|||
previousY = newY; |
|||
} |
|||
|
|||
float quant = AdjustedQuant(quantizationAdjustment); |
|||
float inverseQuant = InverseAdjustedQuant(quantizationAdjustment); |
|||
|
|||
for (int j = 0; j < 3; j++) |
|||
{ |
|||
int c = Loops[j]; |
|||
|
|||
float factor = (c == 0) ? yToX : (c == 1) ? 0 : yToB; |
|||
|
|||
// TODO: lower amount of branches by duplicating code
|
|||
// for i=0 and adding a separate loop for i=1..31
|
|||
for (int i = 0; i < 32; i++) |
|||
{ |
|||
float dctFactor = (i == 0) ? Sqrt2 : 1.0f; |
|||
float inverseDctFactor = (i == 0) ? Sqrt05 : 1.0f; |
|||
float restoredY = spline.ColorDct[1][i] * inverseDctFactor * ChannelWeight[1] * inverseQuant; |
|||
float decorrelated = spline.ColorDct[c][i] - (factor * restoredY); |
|||
spline.ColorDct[c][i] = ConvertToInteger(decorrelated * dctFactor * quant / ChannelWeight[c]); |
|||
} |
|||
} |
|||
|
|||
for (int i = 0; i < 32; i++) |
|||
{ |
|||
float dctFactor = (i == 0) ? Sqrt2 : 1.0f; |
|||
spline.SigmaDct[i] = ConvertToInteger(original.SigmaDct[i] * dctFactor * quant / ChannelWeight[1]); |
|||
} |
|||
|
|||
return spline; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
static int ConvertToInteger(float value) |
|||
{ |
|||
const float max = int.MaxValue - 127f; |
|||
const float min = -max; |
|||
|
|||
return (int)MathF.Round(Math.Clamp(value, min, max)); |
|||
} |
|||
} |
|||
|
|||
public bool Dequantize( |
|||
Configuration configuration, |
|||
PointF startingPoint, |
|||
int quantizationAdjustment, |
|||
float yToX, |
|||
float yToB, |
|||
long imageSize, |
|||
ref long totalEstimatedAreaReached, |
|||
JxlSpline result) |
|||
{ |
|||
long areaLimit = Math.Min(1024 * imageSize * (1L << 32), 1L << 42); |
|||
result.ClearControlPoints(); |
|||
result.ReserveControlPoints(configuration, this.ControlPoints.Length + 1); |
|||
|
|||
float px = MathF.Round(startingPoint.X); |
|||
float py = MathF.Round(startingPoint.Y); |
|||
|
|||
if (!this.ValidateSplinePointPos(px, py)) |
|||
{ |
|||
Debug.Fail("Spline points out of range"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
int currentX = (int)px; |
|||
int currentY = (int)py; |
|||
|
|||
Span<PointF> controlPoints = result.ControlPoints.Span; |
|||
Span<JxlControlPoint> thisControlPoints = this.ControlPoints.Span; |
|||
|
|||
controlPoints[0] = new(currentX, currentY); |
|||
|
|||
int currentDx = 0; // D stands for delta
|
|||
int currentDy = 0; // D stands for delta
|
|||
|
|||
long manhattanDistance = 0; |
|||
|
|||
int length = this.ControlPoints.Length; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
JxlControlPoint point = thisControlPoints[i]; |
|||
currentDx += point.First; |
|||
currentDy += point.Second; |
|||
manhattanDistance = Math.Abs(currentDx) + Math.Abs(currentDy); |
|||
if (manhattanDistance > areaLimit) |
|||
{ |
|||
Debug.Fail("Manhattan distance is too large"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
if (!ValidateSplinePointPos(currentDx, currentDy)) |
|||
{ |
|||
Debug.Fail("Delta points out of range"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
currentX += currentDx; |
|||
currentY += currentDy; |
|||
|
|||
if (!ValidateSplinePointPos(currentX, currentY)) |
|||
{ |
|||
Debug.Fail("Current points out of range"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
controlPoints[i + 1] = new(currentX, currentY); |
|||
} |
|||
|
|||
float inverseQuant = InverseAdjustedQuant(quantizationAdjustment); |
|||
|
|||
for (int c = 0; c < 3; c++) |
|||
{ |
|||
for (int i = 0; i < 32; i++) |
|||
{ |
|||
float inverseDctFactor = (i == 0) ? Sqrt05 : 1.0f; |
|||
result.ColorDct[c][i] = this.ColorDct[c][i] * inverseDctFactor * ChannelWeight[c] * inverseQuant; |
|||
} |
|||
} |
|||
|
|||
for (int i = 0; i < 32; i++) |
|||
{ |
|||
result.ColorDct[0][i] += yToX * result.ColorDct[1][i]; |
|||
result.ColorDct[2][i] += yToB * result.ColorDct[1][i]; |
|||
} |
|||
|
|||
long widthEstimate = 0; |
|||
Span<long> color = stackalloc long[3]; |
|||
|
|||
for (int c = 0; c < 3; c++) |
|||
{ |
|||
for (int i = 0; i < 32; i++) |
|||
{ |
|||
color[c] += (long)MathF.Ceiling(inverseQuant * MathF.Abs(this.ColorDct[c][i])); |
|||
} |
|||
} |
|||
|
|||
color[0] += (long)MathF.Ceiling(MathF.Abs(yToX)) * color[1]; |
|||
color[2] += (long)MathF.Ceiling(MathF.Abs(yToB)) * color[1]; |
|||
|
|||
long maxColor = Math.Max(color[1], Math.Max(color[0], color[2])); |
|||
long logColor = Math.Max(1L, (long)CeilLog2Nonzero(1L + maxColor)); |
|||
float weightLimit = MathF.Ceiling(MathF.Sqrt((float)areaLimit / logColor) / MathF.Max(1, manhattanDistance)); |
|||
|
|||
for (int i = 0; i < 32; i++) |
|||
{ |
|||
float inverseDctFactor = (i == 0) ? Sqrt05 : 1.0f; |
|||
result.SigmaDct[i] = this.SigmaDct[i] * inverseDctFactor * ChannelWeight[3] * inverseQuant; |
|||
float weightF = MathF.Ceiling(inverseQuant * MathF.Abs(this.SigmaDct[i])); |
|||
long weight = (long)Math.Min(weightLimit, Math.Max(1.0f, weightF)); |
|||
widthEstimate += weight * weight * logColor; |
|||
} |
|||
|
|||
totalEstimatedAreaReached = widthEstimate * manhattanDistance; |
|||
if (totalEstimatedAreaReached > areaLimit) |
|||
{ |
|||
Debug.Fail("Total estimated area is too large"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public bool Decode( |
|||
Configuration configuration, |
|||
Span<byte> contextMap, |
|||
JxlAnsSymbolReader decoder, |
|||
JxlBitReader br, |
|||
int maxControlPoints, |
|||
ref int totalControlPoints) |
|||
{ |
|||
int numControlPoints = decoder.ReadHybridUnsignedInteger(NumControlPointsContext, br, contextMap); |
|||
if (numControlPoints > maxControlPoints) |
|||
{ |
|||
Debug.Fail("Too many control points"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
totalControlPoints += numControlPoints; |
|||
|
|||
if (totalControlPoints >= maxControlPoints) |
|||
{ |
|||
Debug.Fail("Too many control points"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
this.ResizeControlPoints(configuration, numControlPoints); |
|||
|
|||
const long deltaLimit = 1L << 30; |
|||
Span<JxlControlPoint> controlPoints = this.ControlPoints.Span; |
|||
|
|||
int length = this.ControlPoints.Length; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
ref JxlControlPoint controlPoint = ref controlPoints[i]; |
|||
|
|||
controlPoint.First = UnpackSigned(decoder.ReadHybridUnsignedInteger(ControlPointsContext, br, contextMap)); |
|||
controlPoint.Second = UnpackSigned(decoder.ReadHybridUnsignedInteger(ControlPointsContext, br, contextMap)); |
|||
|
|||
if (controlPoint.First >= deltaLimit || controlPoint.First <= -deltaLimit || |
|||
controlPoint.Second >= deltaLimit || controlPoint.Second <= -deltaLimit) |
|||
{ |
|||
Debug.Fail("Spline delta-delta is out of bounds"); |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
for (int i = 0; i < this.ColorDct.Length; i++) |
|||
{ |
|||
if (!TryDecodeDct(contextMap, this.ColorDct[i])) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
if (!TryDecodeDct(contextMap, this.SigmaDct)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
|
|||
bool TryDecodeDct(ReadOnlySpan<byte> contextMap, Span<int> dct) |
|||
{ |
|||
const int invalidCoefficient = int.MinValue; |
|||
|
|||
for (int i = 0; i < 32; i++) |
|||
{ |
|||
dct[i] = UnpackSigned(decoder.ReadHybridUnsignedInteger(DctContext, br, contextMap)); |
|||
if (dct[i] == invalidCoefficient) |
|||
{ |
|||
Debug.Fail("The DCT coefficient is invalid"); |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static float AdjustedQuant(int adjustment) |
|||
=> (adjustment >= 0) |
|||
? (1f + (.125f * adjustment)) |
|||
: 1f / (1f - (.125f * adjustment)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static float InverseAdjustedQuant(int adjustment) |
|||
=> (adjustment >= 0) |
|||
? 1f / (1f + (.125f * adjustment)) |
|||
: (1f - (.125f * adjustment)); |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Splines; |
|||
|
|||
internal sealed class JxlSpline : IDisposable |
|||
{ |
|||
private IMemoryOwner<PointF>? controlPoints; |
|||
|
|||
public Memory<PointF> ControlPoints { get; private set; } |
|||
|
|||
public JxlDct32[] ColorDct { get; set; } = []; |
|||
|
|||
public JxlDct32 SigmaDct { get; set; } |
|||
|
|||
public void ClearControlPoints() |
|||
{ |
|||
this.controlPoints?.Dispose(); |
|||
this.controlPoints = null; |
|||
|
|||
this.ControlPoints = Memory<PointF>.Empty; |
|||
} |
|||
|
|||
public void ReserveControlPoints(Configuration configuration, int n) |
|||
{ |
|||
this.ClearControlPoints(); |
|||
|
|||
this.controlPoints = configuration.MemoryAllocator.Allocate<PointF>(n); |
|||
this.ControlPoints = this.controlPoints.Memory; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
this.ClearControlPoints(); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Splines; |
|||
|
|||
internal sealed class JxlSplineDataView |
|||
{ |
|||
public List<JxlQuantizedSpline> Splines { get; set; } = []; |
|||
|
|||
public List<PointF> StartingPoints { get; set; } = []; |
|||
|
|||
public bool HasAny => this.Splines.Count > 0; |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Splines; |
|||
|
|||
internal enum JxlSplineEntropyContext : byte |
|||
{ |
|||
QuantizationAdjustment, |
|||
StartingPosition, |
|||
NumSplines, |
|||
NumControlPoints, |
|||
ControlPoints, |
|||
Dct, |
|||
NumSplineContexts |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Splines; |
|||
|
|||
internal struct JxlSplineSegment |
|||
{ |
|||
public PointF Center { get; set; } |
|||
|
|||
public float MaximumDistance { get; set; } |
|||
|
|||
public float InverseSigma { get; set; } |
|||
|
|||
public float SigmaOver4TimesIntensity { get; set; } |
|||
|
|||
public InlineArray3<float> Color { get; set; } |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Splines; |
|||
|
|||
internal struct JxlSplineSegmentSpan(int startInclusive, int endInclusive) |
|||
{ |
|||
public int StartInclusive { get; set; } = startInclusive; |
|||
|
|||
public int EndInclusive { get; set; } = endInclusive; |
|||
} |
|||
Loading…
Reference in new issue