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