mirror of https://github.com/SixLabors/ImageSharp
Browse Source
- Add decoding of Huffman Codes (see dec_huffman.cc and dec_huffman.h) - Add constructors to JxlImage3* classes - Make JxlColorCorrelationMap.Create 'xyb' parameter use true as a default value - Prototype of DCT quant weight parameters - Add passes shared state (see passes_state.cc and passes_state.h) - Add prototype for image operations (see image_ops.cc and image_ops.h) - Simplify inverse MTF (Move to Front) transform - Add patch context (see patch_dictionary_internal.h) - Add prototype of quantizer weights - Add 2nd prototype of ANS entropy decoding (see dec_ans.cc and dec_ans.h) - Add prototype of patch dictionary decoding (see dec_patch_dictionary.cc and dec_patch_dictionary.h)pull/3153/head
25 changed files with 1132 additions and 78 deletions
@ -0,0 +1,50 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.InteropServices; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
||||
|
|
||||
|
internal sealed class JxlAnsCode |
||||
|
{ |
||||
|
public List<JxlHuffmanDecodingData> HuffmanData { get; set; } = []; |
||||
|
|
||||
|
public List<JxlAnsHybridUIntConfiguration> UIntConfig { get; set; } = []; |
||||
|
|
||||
|
public List<int> DegenerateSymbols { get; set; } = []; |
||||
|
|
||||
|
public bool UsePrefixCode { get; set; } |
||||
|
|
||||
|
public byte LogAlphaSize { get; set; } |
||||
|
|
||||
|
public JxlAnsLz77Parameters Lz77 { get; set; } = new(); |
||||
|
|
||||
|
public int MaxNumBits { get; set; } |
||||
|
|
||||
|
public void UpdateMaxNumBits(int ctx, int symbol) |
||||
|
{ |
||||
|
Span<JxlAnsHybridUIntConfiguration> configs = CollectionsMarshal.AsSpan(this.UIntConfig); |
||||
|
ref JxlAnsHybridUIntConfiguration cfg = ref configs[ctx]; |
||||
|
if (this.Lz77.Enabled && this.Lz77.NonserializedDistanceContext != ctx && symbol >= this.Lz77.MinimumSymbol) |
||||
|
{ |
||||
|
symbol -= (int)this.Lz77.MinimumSymbol; |
||||
|
cfg = ref this.Lz77.GetLengthUIntConfigReference(); |
||||
|
} |
||||
|
|
||||
|
uint splitToken = cfg.SplitToken; |
||||
|
uint msbInToken = cfg.MsbInToken; |
||||
|
uint lsbInToken = cfg.LsbInToken; |
||||
|
uint splitExponent = cfg.SplitExponent; |
||||
|
|
||||
|
if (symbol < splitToken) |
||||
|
{ |
||||
|
this.MaxNumBits = Math.Max(this.MaxNumBits, (int)splitExponent); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint nExtra = splitExponent - (msbInToken + lsbInToken) + (((uint)symbol - splitToken) >> (int)(msbInToken + lsbInToken)); |
||||
|
uint total = msbInToken + lsbInToken + nExtra + 1; |
||||
|
this.MaxNumBits = Math.Max(this.MaxNumBits, (int)total); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
||||
|
|
||||
|
internal sealed class JxlAnsSymbolReader |
||||
|
{ |
||||
|
private const int MaxCheckpointInterval = 512; |
||||
|
|
||||
|
// Use class because the Lz77Window property uses 2KB memory
|
||||
|
private sealed class Checkpoint |
||||
|
{ |
||||
|
public uint State { get; set; } |
||||
|
|
||||
|
public uint NumToCopy { get; set; } |
||||
|
|
||||
|
public uint CopyPos { get; set; } |
||||
|
|
||||
|
public uint NumDecoded { get; set; } |
||||
|
|
||||
|
public uint[] Lz77Window { get; set; } = new uint[MaxCheckpointInterval]; |
||||
|
} |
||||
|
|
||||
|
private readonly JxlAnsEntry[] aliasTables = []; |
||||
|
private JxlHuffmanDecodingData huffmanData; |
||||
|
private bool usePrefixCode; |
||||
|
private uint state = AnsSignature << 16u; |
||||
|
} |
||||
@ -0,0 +1,331 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.IO; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decodes Huffman codes.
|
||||
|
/// </summary>
|
||||
|
internal sealed class JxlHuffmanDecoder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Number of bits that a huffman table uses.
|
||||
|
/// </summary>
|
||||
|
private const int HuffmanTableBits = 8; |
||||
|
|
||||
|
private const int GoalSize = 1 << HuffmanTableBits; |
||||
|
|
||||
|
public const int CodeLengthCodes = 18; |
||||
|
|
||||
|
public const int DefaultCodeLength = 8; |
||||
|
|
||||
|
public const int CodeLengthRepeatCode = 16; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Static Huffman codes for code length code lengths.
|
||||
|
/// </summary>
|
||||
|
private static readonly JxlHuffmanCode[] CodeLengthCodeLengthsCodes = |
||||
|
[ |
||||
|
new(2, 0), new(2, 4), new(2, 3), new(3, 2), new(2, 0), new(2, 4), new(2, 3), new(4, 1), |
||||
|
new(2, 0), new(2, 4), new(2, 3), new(3, 2), new(2, 0), new(2, 4), new(2, 3), new(4, 5), |
||||
|
]; |
||||
|
|
||||
|
private static ReadOnlySpan<byte> CodeLengthCodeOrder => |
||||
|
[ |
||||
|
1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
||||
|
]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the list of huffman codes.
|
||||
|
/// </summary>
|
||||
|
public JxlHuffmanCode[] Table { get; set; } = []; |
||||
|
|
||||
|
public static bool ReadHuffmanCodeLengths(Span<byte> codeLengthCodeLengths, int numSymbols, Span<byte> codeLengths, JxlBitReader br) |
||||
|
{ |
||||
|
int symbol = 0; |
||||
|
int prevCodeLen = DefaultCodeLength; |
||||
|
int repeat = 0; |
||||
|
int repeatCodeLen = 0; |
||||
|
int space = 32768; |
||||
|
|
||||
|
Span<JxlHuffmanCode> table = stackalloc JxlHuffmanCode[32]; |
||||
|
Span<ushort> counts = stackalloc ushort[16]; |
||||
|
table.Clear(); |
||||
|
counts.Clear(); |
||||
|
|
||||
|
for (int i = 0; i < CodeLengthCodes; i++) |
||||
|
{ |
||||
|
counts[codeLengthCodeLengths[i]]++; |
||||
|
} |
||||
|
|
||||
|
if (JxlHuffman.BuildHuffmanTable(table, 5, codeLengthCodeLengths, counts) == 0) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
while (symbol < numSymbols && space > 0) |
||||
|
{ |
||||
|
JxlHuffmanCode code = table[(int)br.PeekBits32(5u)]; |
||||
|
br.SkipBits32(code.Bits); |
||||
|
byte codeLength = (byte)code.Value; // It is indeed converted from ushort to byte
|
||||
|
|
||||
|
if (codeLength < CodeLengthRepeatCode) |
||||
|
{ |
||||
|
repeat = 0; |
||||
|
codeLengths[symbol++] = codeLength; |
||||
|
if (codeLength != 0) |
||||
|
{ |
||||
|
prevCodeLen = codeLength; |
||||
|
space -= 32768 >> codeLength; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
int extraBits = codeLength - 14; |
||||
|
byte newLength = 0; |
||||
|
if (codeLength == CodeLengthRepeatCode) |
||||
|
{ |
||||
|
newLength = (byte)prevCodeLen; |
||||
|
} |
||||
|
|
||||
|
if (repeatCodeLen != newLength) |
||||
|
{ |
||||
|
repeat = 0; |
||||
|
repeatCodeLen = newLength; |
||||
|
} |
||||
|
|
||||
|
int oldRepeat = repeat; |
||||
|
|
||||
|
if (repeat > 0) |
||||
|
{ |
||||
|
repeat -= 2; |
||||
|
repeat <<= extraBits; |
||||
|
} |
||||
|
|
||||
|
repeat += (int)br.ReadBits32((uint)extraBits) + 3; |
||||
|
int repeatDelta = repeat - oldRepeat; |
||||
|
|
||||
|
if (symbol + repeatDelta > numSymbols) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
codeLengths.Slice(symbol, repeatDelta).Fill((byte)repeatCodeLen); |
||||
|
symbol += repeatDelta; |
||||
|
if (repeatCodeLen != 0) |
||||
|
{ |
||||
|
space -= repeatDelta << (15 - repeatCodeLen); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (space != 0) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
codeLengths[symbol..].Clear(); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reads a simple Huffman code.
|
||||
|
/// </summary>
|
||||
|
/// <param name="alphabetSize">Alphabet size (256 at most)</param>
|
||||
|
/// <param name="br">Bit-stream reader</param>
|
||||
|
/// <param name="table">Output table (must have at most 8 items)</param>
|
||||
|
/// <returns>Status of the operation</returns>
|
||||
|
public static bool ReadSimpleCode(int alphabetSize, JxlBitReader br, Span<JxlHuffmanCode> table) |
||||
|
{ |
||||
|
int maxBits = (alphabetSize > 1) ? FloorLog2Nonzero(alphabetSize - 1) + 1 : 0; |
||||
|
uint symbolCount = br.ReadBits32(2u) + 1u; |
||||
|
|
||||
|
Span<ushort> symbols = stackalloc ushort[4]; |
||||
|
symbols.Clear(); // Clearing is necessary. Not every value will be initialized.
|
||||
|
|
||||
|
for (int i = 0; i < symbolCount; i++) |
||||
|
{ |
||||
|
uint symbol = br.ReadBits32((uint)maxBits); |
||||
|
if (symbol >= alphabetSize) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
symbols[i] = (ushort)symbol; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < symbolCount - 1; i++) |
||||
|
{ |
||||
|
for (int j = i + 1; j < symbolCount; j++) |
||||
|
{ |
||||
|
if (symbols[i] == symbols[j]) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (symbolCount == 4) |
||||
|
{ |
||||
|
symbolCount += br.ReadBits32(1u); |
||||
|
} |
||||
|
|
||||
|
int tableSize = 1; |
||||
|
switch (symbolCount) |
||||
|
{ |
||||
|
case 1: |
||||
|
table[0] = new(0, symbols[0]); |
||||
|
break; |
||||
|
|
||||
|
case 2: |
||||
|
if (symbols[0] > symbols[1]) |
||||
|
{ |
||||
|
SwapSymbols(0, 1, symbols); |
||||
|
} |
||||
|
|
||||
|
table[0] = new(1, symbols[0]); |
||||
|
table[1] = new(1, symbols[1]); |
||||
|
tableSize = 2; |
||||
|
break; |
||||
|
|
||||
|
case 3: |
||||
|
if (symbols[1] > symbols[2]) |
||||
|
{ |
||||
|
SwapSymbols(1, 2, symbols); |
||||
|
} |
||||
|
|
||||
|
table[0] = new(1, symbols[0]); |
||||
|
table[2] = new(1, symbols[0]); |
||||
|
table[1] = new(2, symbols[1]); |
||||
|
table[3] = new(2, symbols[2]); |
||||
|
tableSize = 4; |
||||
|
break; |
||||
|
|
||||
|
case 4: |
||||
|
for (int i = 0; i < 3; i++) |
||||
|
{ |
||||
|
for (int j = i + 1; j < 4; j++) |
||||
|
{ |
||||
|
if (symbols[i] > symbols[j]) |
||||
|
{ |
||||
|
SwapSymbols(i, j, symbols); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
table[0] = new(2, symbols[0]); |
||||
|
table[2] = new(2, symbols[1]); |
||||
|
table[1] = new(2, symbols[2]); |
||||
|
table[3] = new(2, symbols[3]); |
||||
|
tableSize = 4; |
||||
|
break; |
||||
|
|
||||
|
case 5: |
||||
|
if (symbols[2] > symbols[3]) |
||||
|
{ |
||||
|
SwapSymbols(2, 3, symbols); |
||||
|
} |
||||
|
|
||||
|
table[0] = new(1, symbols[0]); |
||||
|
table[1] = new(2, symbols[1]); |
||||
|
table[2] = new(1, symbols[0]); |
||||
|
table[3] = new(3, symbols[2]); |
||||
|
table[4] = new(1, symbols[0]); |
||||
|
table[5] = new(2, symbols[1]); |
||||
|
table[6] = new(1, symbols[0]); |
||||
|
table[7] = new(3, symbols[3]); |
||||
|
tableSize = 8; |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
// This should be unreachable.
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
while (tableSize != GoalSize) |
||||
|
{ |
||||
|
table[tableSize..].CopyTo(table); |
||||
|
tableSize <<= 1; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public bool ReadFromBitStream(int alphabetSize, JxlBitReader br) |
||||
|
{ |
||||
|
if (alphabetSize > (1 << JxlAnsConstants.PrefixMaxBits)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
uint simpleCodeOrSkip = br.ReadBits32(2u); |
||||
|
if (simpleCodeOrSkip == 1u) |
||||
|
{ |
||||
|
this.Table = new JxlHuffmanCode[GoalSize]; |
||||
|
return ReadSimpleCode(alphabetSize, br, this.Table); |
||||
|
} |
||||
|
|
||||
|
// The alphabet size is at most 256
|
||||
|
Span<byte> codeLengths = stackalloc byte[alphabetSize]; |
||||
|
codeLengths.Clear(); // Zero-initialized in reference software
|
||||
|
|
||||
|
Span<byte> codeLengthCodeLengths = stackalloc byte[CodeLengthCodes]; |
||||
|
codeLengthCodeLengths.Clear(); // Zero-initialized in reference software
|
||||
|
|
||||
|
int space = 32; |
||||
|
int numCodes = 0; |
||||
|
|
||||
|
for (uint i = simpleCodeOrSkip; i < CodeLengthCodes && space > 0; i++) |
||||
|
{ |
||||
|
int codeLengthIndex = CodeLengthCodeOrder[(int)i]; |
||||
|
JxlHuffmanCode huff = CodeLengthCodeLengthsCodes[(int)br.PeekBits32(4u)]; |
||||
|
br.SkipBits32(huff.Bits); |
||||
|
byte value = (byte)huff.Value; // It's indeed converted from ushort to byte
|
||||
|
codeLengthCodeLengths[codeLengthIndex] = value; |
||||
|
|
||||
|
if (value != 0) |
||||
|
{ |
||||
|
space -= 32 >> value; |
||||
|
numCodes++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool ok = (numCodes == 1 || space == 0) && ReadHuffmanCodeLengths(codeLengthCodeLengths, alphabetSize, codeLengths, br); |
||||
|
|
||||
|
if (!ok) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
Span<ushort> counts = stackalloc ushort[16]; |
||||
|
counts.Clear(); // Zero-initialized
|
||||
|
|
||||
|
this.Table = new JxlHuffmanCode[alphabetSize + 376]; |
||||
|
uint tableSize = JxlHuffman.BuildHuffmanTable(this.Table, HuffmanTableBits, codeLengths, counts); |
||||
|
|
||||
|
this.Table = this.Table[..(int)tableSize]; |
||||
|
|
||||
|
return tableSize > 0; |
||||
|
} |
||||
|
|
||||
|
public ushort ReadSymbol(JxlBitReader br) |
||||
|
{ |
||||
|
Span<JxlHuffmanCode> table = this.Table.AsSpan()[(int)br.PeekBits32(HuffmanTableBits)..]; |
||||
|
int bitCount = table[0].Bits; |
||||
|
if (bitCount > HuffmanTableBits) |
||||
|
{ |
||||
|
br.SkipBits32(HuffmanTableBits); |
||||
|
bitCount -= HuffmanTableBits; |
||||
|
table = table[(int)(table[0].Value + br.PeekBits32((uint)bitCount))..]; |
||||
|
} |
||||
|
|
||||
|
br.SkipBits32(table[0].Bits); |
||||
|
return table[0].Value; |
||||
|
} |
||||
|
|
||||
|
private static void SwapSymbols(int i, int j, Span<ushort> symbols) => RuntimeUtility.Swap(ref symbols[i], ref symbols[j]); |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
||||
|
|
||||
|
internal enum JxlPatchBlendMode : byte |
||||
|
{ |
||||
|
None, |
||||
|
Replace, |
||||
|
Add, |
||||
|
Multiply, |
||||
|
BlendAbove, |
||||
|
BlendBelow, |
||||
|
AlphaWeightedAddAbove, |
||||
|
AlphaWeightedAddBelow |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
||||
|
|
||||
|
internal struct JxlPatchBlending |
||||
|
{ |
||||
|
public JxlPatchBlendMode Mode; |
||||
|
public int AlphaChannel; |
||||
|
public bool Clamp; |
||||
|
} |
||||
@ -0,0 +1,256 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
||||
|
|
||||
|
internal sealed class JxlPatchDictionary |
||||
|
{ |
||||
|
private struct PatchTreeNode |
||||
|
{ |
||||
|
public long LeftChild; |
||||
|
public long RightChild; |
||||
|
public int YCenter; |
||||
|
public int Start; |
||||
|
public int Count; |
||||
|
} |
||||
|
|
||||
|
private struct SortedPatch |
||||
|
{ |
||||
|
public int First; |
||||
|
public int Second; |
||||
|
} |
||||
|
|
||||
|
private readonly JxlReferenceFrame[] referenceFrames = new JxlReferenceFrame[4]; |
||||
|
private readonly List<JxlPatchPosition> positions = []; |
||||
|
private readonly List<JxlPatchReferencePosition> referencePositions = []; |
||||
|
private readonly List<JxlPatchBlending> blendings = []; |
||||
|
private int blendingsStride; |
||||
|
private readonly List<PatchTreeNode> patchTree = []; |
||||
|
private readonly List<int> numPatches = []; |
||||
|
private readonly List<SortedPatch> sortedPatchesY0 = []; |
||||
|
private readonly List<SortedPatch> sortedPatchesY1 = []; |
||||
|
|
||||
|
public bool HasAny => this.positions.Count > 0; |
||||
|
|
||||
|
public void Clear() |
||||
|
{ |
||||
|
this.positions.Clear(); |
||||
|
ComputePatchTree(); |
||||
|
} |
||||
|
|
||||
|
public void Decode( |
||||
|
JxlMemoryManager memoryManager, |
||||
|
JxlBitReader br, |
||||
|
ulong xsize, |
||||
|
ulong ysize, |
||||
|
ulong numExtraChannels, |
||||
|
ref bool usesExtraChannels) |
||||
|
{ |
||||
|
this.positions.Clear(); |
||||
|
this.blendingsStride = (int)(numExtraChannels + 1); |
||||
|
|
||||
|
List<byte> contextMap = []; |
||||
|
var code = new JxlAnsCode(); |
||||
|
|
||||
|
var status = DecodeHistograms( |
||||
|
memoryManager, |
||||
|
br, |
||||
|
PatchDictionaryContexts, |
||||
|
code, |
||||
|
contextMap); |
||||
|
|
||||
|
JxlAnsSymbolReader decoder = JxlAnsSymbolReader.Create(code, br); |
||||
|
|
||||
|
ulong ReadNum(int context) |
||||
|
=> decoder.ReadHybridUint(context, br, contextMap); |
||||
|
|
||||
|
ulong numRefPatch = ReadNum(kNumRefPatchContext); |
||||
|
|
||||
|
ulong numPixels = xsize * ysize; |
||||
|
ulong maxRefPatches = 1024 + (numPixels / 4); |
||||
|
ulong maxPatches = maxRefPatches * 4; |
||||
|
ulong maxBlendingInfos = maxPatches * 4; |
||||
|
|
||||
|
if (numRefPatch > maxRefPatches) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Too many patches in dictionary"); |
||||
|
} |
||||
|
|
||||
|
ulong totalPatches = 0; |
||||
|
ulong nextSize = 1; |
||||
|
|
||||
|
for (ulong id = 0; id < numRefPatch; id++) |
||||
|
{ |
||||
|
JxlPatchReferencePosition refPos = new() |
||||
|
{ |
||||
|
Ref = ReadNum(kReferenceFrameContext) |
||||
|
}; |
||||
|
|
||||
|
if (refPos.Ref >= kMaxNumReferenceFrames || this.referenceFrames[(int)refPos.Ref].Frame.XSize == 0) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Invalid reference frame ID"); |
||||
|
} |
||||
|
|
||||
|
if (!this.referenceFrames[refPos.Ref].IsInXYB) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Patches cannot use frames saved post color transforms"); |
||||
|
} |
||||
|
|
||||
|
JxlImageBundle ib = this.referenceFrames[refPos.Ref].Frame; |
||||
|
|
||||
|
refPos.X0 = ReadNum(kPatchReferencePositionContext); |
||||
|
refPos.Y0 = ReadNum(kPatchReferencePositionContext); |
||||
|
refPos.XSize = ReadNum(kPatchSizeContext) + 1; |
||||
|
refPos.YSize = ReadNum(kPatchSizeContext) + 1; |
||||
|
|
||||
|
if (refPos.X0 + refPos.XSize > ib.XSize) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Invalid position specified in reference frame"); |
||||
|
} |
||||
|
|
||||
|
if (refPos.Y0 + refPos.YSize > ib.YSize) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Invalid position specified in reference frame"); |
||||
|
} |
||||
|
|
||||
|
ulong idCount = ReadNum(kPatchCountContext); |
||||
|
|
||||
|
if (idCount > maxPatches) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Too many patches in dictionary"); |
||||
|
} |
||||
|
|
||||
|
idCount++; |
||||
|
|
||||
|
totalPatches += idCount; |
||||
|
|
||||
|
if (totalPatches > maxPatches) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Too many patches in dictionary"); |
||||
|
} |
||||
|
|
||||
|
if (nextSize < totalPatches) |
||||
|
{ |
||||
|
nextSize *= 2; |
||||
|
nextSize = Math.Min(nextSize, maxPatches); |
||||
|
} |
||||
|
|
||||
|
if (nextSize * (ulong)this.blendingsStride > maxBlendingInfos) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Too many patches in dictionary"); |
||||
|
} |
||||
|
|
||||
|
_ = this.blendings.EnsureCapacity((int)nextSize); |
||||
|
_ = this.blendings.EnsureCapacity((int)(nextSize * (ulong)this.blendingsStride)); |
||||
|
|
||||
|
bool chooseAlpha = numExtraChannels > 1; |
||||
|
|
||||
|
for (ulong i = 0; i < idCount; i++) |
||||
|
{ |
||||
|
JxlPatchPosition pos = new() |
||||
|
{ |
||||
|
ReferencePositionIndex = this.referencePositions.Count |
||||
|
}; |
||||
|
|
||||
|
if (i == 0) |
||||
|
{ |
||||
|
pos.X = ReadNum(kPatchPositionContext); |
||||
|
pos.Y = ReadNum(kPatchPositionContext); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
long deltaX = JxlPackSigned.UnpackSigned(ReadNum(kPatchOffsetContext)); |
||||
|
|
||||
|
if (deltaX < 0 && (int)(-deltaX) > this.positions[^1].X) |
||||
|
{ |
||||
|
throw new InvalidOperationException($"Invalid patch: negative x coordinate ({this.positions[^1].X}, delta {deltaX})"); |
||||
|
} |
||||
|
|
||||
|
pos.X = (int)(this.positions[^1].X + deltaX); |
||||
|
|
||||
|
long deltaY = JxlPackSigned.UnpackSigned(ReadNum(kPatchOffsetContext)); |
||||
|
|
||||
|
if (deltaY < 0 && (int)(-deltaY) > this.positions[^1].Y) |
||||
|
{ |
||||
|
throw new InvalidOperationException($"Invalid patch: negative y coordinate ({this.positions[^1].Y}, delta {deltaY})"); |
||||
|
} |
||||
|
|
||||
|
pos.Y = (int)(this.positions[^1].Y + deltaY); |
||||
|
} |
||||
|
|
||||
|
if (pos.X + refPos.XSize > (int)xsize) |
||||
|
{ |
||||
|
throw new InvalidOperationException($"Invalid patch x: {pos.X} + {refPos.XSize} > {xsize}"); |
||||
|
} |
||||
|
|
||||
|
if (pos.Y + refPos.YSize > (int)ysize) |
||||
|
{ |
||||
|
throw new InvalidOperationException($"Invalid patch y: {pos.Y} + {refPos.YSize} > {ysize}"); |
||||
|
} |
||||
|
|
||||
|
for (int j = 0; j < this.blendingsStride; j++) |
||||
|
{ |
||||
|
uint blendMode = (uint)ReadNum(kPatchBlendModeContext); |
||||
|
|
||||
|
if (blendMode >= kNumPatchBlendModes) |
||||
|
{ |
||||
|
throw new InvalidOperationException($"Invalid patch blend mode: {blendMode}"); |
||||
|
} |
||||
|
|
||||
|
JxlPatchBlending info = new() |
||||
|
{ |
||||
|
Mode = (JxlPatchBlendMode)blendMode |
||||
|
}; |
||||
|
|
||||
|
if (UsesAlpha(info.Mode)) |
||||
|
{ |
||||
|
usesExtraChannels = true; |
||||
|
} |
||||
|
|
||||
|
if (info.Mode != JxlPatchBlendMode.None && j > 0) |
||||
|
{ |
||||
|
usesExtraChannels = true; |
||||
|
} |
||||
|
|
||||
|
if (UsesAlpha(info.Mode) && chooseAlpha) |
||||
|
{ |
||||
|
info.AlphaChannel = (uint)ReadNum(kPatchAlphaChannelContext); |
||||
|
|
||||
|
if (info.AlphaChannel >= (int)numExtraChannels) |
||||
|
{ |
||||
|
throw new InvalidOperationException($"Invalid alpha channel for blending: {info.AlphaChannel} out of {numExtraChannels}"); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
info.AlphaChannel = 0; |
||||
|
} |
||||
|
|
||||
|
if (UsesClamp(info.Mode)) |
||||
|
{ |
||||
|
info.Clamp = ReadNum(kPatchClampContext) != 0; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
info.Clamp = false; |
||||
|
} |
||||
|
|
||||
|
this.blendings.Add(info); |
||||
|
} |
||||
|
|
||||
|
this.positions.Add(pos); |
||||
|
} |
||||
|
|
||||
|
this.positions.Add(refPos); |
||||
|
} |
||||
|
|
||||
|
this.positions.TrimExcess(); |
||||
|
|
||||
|
if (!decoder.CheckAnsFinalState()) |
||||
|
{ |
||||
|
throw new InvalidOperationException("ANS checksum failure."); |
||||
|
} |
||||
|
|
||||
|
this.ComputePatchTree(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
||||
|
|
||||
|
internal struct JxlPatchPosition |
||||
|
{ |
||||
|
public int X; |
||||
|
public int Y; |
||||
|
public int ReferencePositionIndex; |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
||||
|
|
||||
|
internal struct JxlPatchReferencePosition |
||||
|
{ |
||||
|
public int Ref; |
||||
|
public int X0; |
||||
|
public int Y0; |
||||
|
public int XSize; |
||||
|
public int YSize; |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
internal sealed class JxlDctQuantWeightParameters |
||||
|
{ |
||||
|
private const int Log2MaxDistanceBands = 4; |
||||
|
private const int MaxDistanceBands = 1 + (1 << Log2MaxDistanceBands); |
||||
|
|
||||
|
private int numDistanceBands; |
||||
|
private readonly float[][] distanceBands; |
||||
|
|
||||
|
public JxlDctQuantWeightParameters() |
||||
|
{ |
||||
|
this.distanceBands = new float[3][]; |
||||
|
for (int i = 0; i < 3; i++) |
||||
|
{ |
||||
|
this.distanceBands[i] = new float[MaxDistanceBands]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public JxlDctQuantWeightParameters(float[][] distanceBands, int numDistanceBands) |
||||
|
{ |
||||
|
this.numDistanceBands = numDistanceBands; |
||||
|
this.distanceBands = distanceBands; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Image features for the JPEG XL passes decoder
|
||||
|
/// </summary>
|
||||
|
internal sealed class JxlImageFeatures |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets noise parameters for the passes decoder
|
||||
|
/// </summary>
|
||||
|
public JxlNoiseParameters NoiseParameters { get; set; } = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets patch dictionary for the passes decoder
|
||||
|
/// </summary>
|
||||
|
public JxlPatchDictionary PatchDictionary { get; set; } = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets splines for the passes decoder
|
||||
|
/// </summary>
|
||||
|
public JxlSplines Splines { get; set; } = new(); |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
internal static class JxlImageOperations |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Returns true if first image has same width and height as the second image.
|
||||
|
/// </summary>
|
||||
|
/// <param name="a">First image</param>
|
||||
|
/// <param name="b">Second image</param>
|
||||
|
/// <returns>True if width and height is equal.</returns>
|
||||
|
public static bool SameSize(JxlPlaneBase a, JxlPlaneBase b) => a.XSize == b.XSize && a.YSize == b.YSize; |
||||
|
|
||||
|
public static bool CopyImage<T>(JxlPlane<T> from, JxlPlane<T> to) |
||||
|
where T : unmanaged |
||||
|
{ |
||||
|
if (!SameSize(from, to)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (from.XSize == 0 || from.YSize == 0) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
for (int y = 0; y < from.YSize; y++) |
||||
|
{ |
||||
|
Span<T> rowFrom = from.GetRow(y); |
||||
|
Span<T> rowTo = to.GetRow(y); |
||||
|
rowFrom.CopyTo(rowTo); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public static bool CopyImageTo<T>(Rectangle rectFrom, JxlPlane<T> from, Rectangle rectTo, JxlPlane<T> to) |
||||
|
where T : unmanaged |
||||
|
{ |
||||
|
if (rectFrom != rectTo) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,111 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
internal class JxlPassesSharedState |
||||
|
{ |
||||
|
public JxlCodecMetadata CodecMetadata { get; set; } = new(); |
||||
|
|
||||
|
public JxlFrameDimensions FrameDimensions { get; set; } |
||||
|
|
||||
|
public JxlAcStrategyImage AcStrategy { get; set; } |
||||
|
|
||||
|
public JxlDequantMatrices Matrices { get; set; } = new(); |
||||
|
|
||||
|
public JxlQuantizer Quantizer { get; set; } |
||||
|
|
||||
|
public JxlImageI RawQuantField { get; set; } |
||||
|
|
||||
|
public JxlImageB EpfSharpness { get; set; } |
||||
|
|
||||
|
public JxlColorCorrelationMap ColorMap { get; set; } |
||||
|
|
||||
|
public JxlImageFeatures ImageFeatures { get; set; } = new(); |
||||
|
|
||||
|
public int CoeffOrderSize { get; set; } |
||||
|
|
||||
|
public List<byte> CoeffOrders { get; set; } = []; |
||||
|
|
||||
|
public JxlImageB QuantDc { get; set; } |
||||
|
|
||||
|
public JxlImage3F DcStorage { get; set; } |
||||
|
|
||||
|
public JxlImage3F Dc { get; set; } |
||||
|
|
||||
|
public JxlBlockContextMap BlockContextMap { get; set; } = new(); |
||||
|
|
||||
|
public JxlImage3F[] DcFrames { get; set; } = new JxlImage3F[4]; |
||||
|
|
||||
|
public JxlReferenceFrame[] ReferenceFrames { get; set; } = new JxlReferenceFrame[4]; |
||||
|
|
||||
|
public int NumHistograms { get; set; } |
||||
|
|
||||
|
public JxlPassesSharedState(Configuration configuration, JxlFrameHeader frameHeader, bool encoder) |
||||
|
{ |
||||
|
if (frameHeader.Metadata is null) |
||||
|
{ |
||||
|
throw new InvalidOperationException("The frame header metadata is missing"); |
||||
|
} |
||||
|
|
||||
|
this.CodecMetadata = frameHeader.Metadata; |
||||
|
this.FrameDimensions = frameHeader.FrameDimensions; |
||||
|
this.ImageFeatures.PatchDictionary.SetShared(this.ImageFeatures.ReferenceFrames); |
||||
|
|
||||
|
JxlFrameDimensions dimensions = frameHeader.FrameDimensions; |
||||
|
|
||||
|
this.AcStrategy = JxlAcStrategyImage.Create(configuration, dimensions.XSizeBlocks, dimensions.YSizeBlocks); |
||||
|
this.RawQuantField = new JxlImageI(configuration, dimensions.XSizeBlocks, dimensions.YSizeBlocks); |
||||
|
this.EpfSharpness = new JxlImageB(configuration, dimensions.XSizeBlocks, dimensions.YSizeBlocks); |
||||
|
this.ColorMap = JxlColorCorrelationMap.Create(configuration, dimensions.XSize, dimensions.YSize); |
||||
|
|
||||
|
this.CoeffOrderSize = JxlCoefficientOrder.CoefficientOrderMaxSize; |
||||
|
|
||||
|
if (encoder && |
||||
|
this.CoeffOrders.Count < (frameHeader.Passes.NumPasses & JxlCoefficientOrder.CoefficientOrderMaxSize) && |
||||
|
frameHeader.Encoding == JxlFrameEncoding.VarDct) |
||||
|
{ |
||||
|
// we add the padding to CoeffOrders so its length is equal to the variable upperBound
|
||||
|
int upperBound = frameHeader.Passes.NumPasses & JxlCoefficientOrder.CoefficientOrderMaxSize; |
||||
|
int length = this.CoeffOrders.Count; |
||||
|
int delta = upperBound - length; |
||||
|
|
||||
|
for (int i = 0; i < delta; i++) |
||||
|
{ |
||||
|
this.CoeffOrders.Add(0); // default constant
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.QuantDc = new JxlImageB(configuration, dimensions.XSizeBlocks, dimensions.YSizeBlocks); |
||||
|
|
||||
|
bool useDcFrame = (frameHeader.Flags & (ulong)JxlFrameHeaderFlags.Dc) != 0; |
||||
|
if (!encoder && useDcFrame) |
||||
|
{ |
||||
|
if (frameHeader.DcLevel == 4) |
||||
|
{ |
||||
|
throw new InvalidOperationException("DC level for DC frames cannot be equal to 4"); |
||||
|
} |
||||
|
|
||||
|
this.DcStorage = new JxlImage3F(); |
||||
|
this.Dc = this.DcFrames[(int)frameHeader.DcLevel]; |
||||
|
|
||||
|
if (this.Dc.XSize == 0) |
||||
|
{ |
||||
|
throw new InvalidOperationException("DC frame was specified for DC Level = " + frameHeader.DcLevel + ", but frame wasn't decoded with level " + frameHeader.DcLevel + 1); |
||||
|
} |
||||
|
|
||||
|
this.QuantDc.Clear(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.DcStorage = new JxlImage3F(configuration, dimensions.XSizeBlocks, dimensions.YSizeBlocks); |
||||
|
this.Dc = this.DcStorage; |
||||
|
} |
||||
|
|
||||
|
this.Quantizer = new(this.Matrices); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Context numbers for patch decoding
|
||||
|
/// </summary>
|
||||
|
internal enum JxlPatchContext : byte |
||||
|
{ |
||||
|
NumRefPatch = 0, |
||||
|
|
||||
|
ReferenceFrame = 1, |
||||
|
|
||||
|
PatchSize = 2, |
||||
|
|
||||
|
PatchReferencePosition = 3, |
||||
|
|
||||
|
PatchPosition = 4, |
||||
|
|
||||
|
PatchBlendMode = 5, |
||||
|
|
||||
|
PatchOffset = 6, |
||||
|
|
||||
|
PatchCount = 7, |
||||
|
|
||||
|
PatchAlphaChannel = 8, |
||||
|
|
||||
|
PatchClamp = 9, |
||||
|
|
||||
|
NumPatchDictionaryContexts |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
internal static class JxlQuantWeights |
||||
|
{ |
||||
|
public const int MaxQuantTableSize = JxlAcStrategy.MaximumCoefficientArea; |
||||
|
|
||||
|
public const int NumPredefinedTables = 1; |
||||
|
|
||||
|
public const int CeilLog2NumPredefinedTables = 0; |
||||
|
|
||||
|
public const int Log2NumQuantModes = 3; |
||||
|
} |
||||
Loading…
Reference in new issue