mirror of https://github.com/SixLabors/ImageSharp
1 changed files with 196 additions and 0 deletions
@ -0,0 +1,196 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.ContextPrediction; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Primitives; |
|||
using Tree = System.Collections.Generic.List<SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.JxlPropertyDecisionNode>; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding; |
|||
|
|||
internal static class JxlMaDecoder |
|||
{ |
|||
private enum NextAction |
|||
{ |
|||
CheckAndGoLeft, |
|||
GoRight, |
|||
Pop |
|||
} |
|||
|
|||
public static void ValidateTree(Tree tree) |
|||
{ |
|||
const int heightLimit = 2048; |
|||
|
|||
if (tree.Count == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int numProperties = tree.Max(x => x.Property + 1); |
|||
Span<(int First, int Second)> propertyRanges = stackalloc (int First, int Second)[numProperties]; |
|||
|
|||
for (int i = 0; i < numProperties; i++) |
|||
{ |
|||
propertyRanges[i].First = int.MinValue; |
|||
propertyRanges[i].Second = int.MaxValue; |
|||
} |
|||
|
|||
Stack<WorkItem> stack = []; |
|||
stack.Push(new WorkItem(0, 0, 0, NextAction.CheckAndGoLeft)); |
|||
|
|||
while (stack.Count > 0) |
|||
{ |
|||
if (stack.Count > heightLimit) |
|||
{ |
|||
throw new InvalidOperationException("Tree too tall"); |
|||
} |
|||
|
|||
WorkItem item = stack.Peek(); |
|||
JxlPropertyDecisionNode node = tree[item.NodeIndex]; |
|||
|
|||
switch (item.Action) |
|||
{ |
|||
case NextAction.CheckAndGoLeft: |
|||
{ |
|||
int p = node.Property; |
|||
if (p == -1) |
|||
{ |
|||
_ = stack.Pop(); |
|||
continue; |
|||
} |
|||
|
|||
int v = node.SplitValue; |
|||
int l = propertyRanges[p].First; |
|||
int u = propertyRanges[p].Second; |
|||
|
|||
if (l > v || u <= v) |
|||
{ |
|||
throw new InvalidOperationException("Invalid tree"); |
|||
} |
|||
|
|||
item.OriginalL = l; |
|||
item.OriginalU = u; |
|||
item.Action = NextAction.GoRight; |
|||
propertyRanges[node.Property].First = node.SplitValue + 1; |
|||
|
|||
stack.Push(new WorkItem(node.LeftChild, 0, 0, NextAction.CheckAndGoLeft)); |
|||
continue; |
|||
} |
|||
|
|||
case NextAction.GoRight: |
|||
item.Action = NextAction.Pop; |
|||
propertyRanges[node.Property].First = item.OriginalL; |
|||
propertyRanges[node.Property].Second = node.SplitValue; |
|||
stack.Push(new WorkItem(node.LeftChild, 0, 0, NextAction.CheckAndGoLeft)); |
|||
continue; |
|||
|
|||
case NextAction.Pop: |
|||
propertyRanges[node.Property].Second = item.OriginalU; |
|||
_ = stack.Pop(); |
|||
continue; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void DecodeTree(JxlBitReader br, JxlAnsSymbolReader reader, List<byte> contextMap, Tree tree, int treeSizeLimit) |
|||
{ |
|||
int leafId = 0; |
|||
int toDecode = 1; |
|||
tree.Clear(); |
|||
|
|||
while (toDecode > 0) |
|||
{ |
|||
if (tree.Count > treeSizeLimit) |
|||
{ |
|||
throw new InvalidOperationException("Tree is too large"); |
|||
} |
|||
|
|||
toDecode--; |
|||
int prop1 = reader.ReadHybridUint(JxlMaTreeContext.Property, br, contextMap); |
|||
if (prop1 > 256) |
|||
{ |
|||
throw new InvalidOperationException("Invalid tree property value"); |
|||
} |
|||
|
|||
int property = prop1 - 1; |
|||
if (property == -1) |
|||
{ |
|||
int predictor = reader.ReadHybridUint(JxlMaTreeContext.Predictor, br, contextMap); |
|||
|
|||
if (predictor >= JxlPredictorFacts.ModularPredictors) |
|||
{ |
|||
throw new InvalidOperationException("Invalid predictor"); |
|||
} |
|||
|
|||
int predictor_offset = JxlPackSigned.UnpackSigned(reader.ReadHybridUint(JxlMaTreeContext.Offset, br, contextMap)); |
|||
int mul_log = reader.ReadHybridUint(JxlMaTreeContext.MultiplierLog, br, contextMap); |
|||
|
|||
if (mul_log >= 31) |
|||
{ |
|||
throw new InvalidOperationException("Invalid multiplier logarithm"); |
|||
} |
|||
|
|||
int mul_bits = reader.ReadHybridUint(JxlMaTreeContext.MultiplierBits, br, contextMap); |
|||
|
|||
if (mul_bits >= (1 << (31 - mul_log)) - 1) |
|||
{ |
|||
throw new InvalidOperationException("Invalid multiplier"); |
|||
} |
|||
|
|||
int multiplier = (mul_bits + 1) << mul_log; |
|||
JxlPredictor p = (JxlPredictor)predictor; |
|||
|
|||
tree.Add(new JxlPropertyDecisionNode(-1, 0, leafId, 0, p, predictor_offset, multiplier)); |
|||
leafId++; |
|||
continue; |
|||
} |
|||
|
|||
int splitval = JxlPackSigned.UnpackSigned(reader.ReadHybridUint(JxlMaTreeContext.SplitValue, br, contextMap)); |
|||
tree.Add( |
|||
new JxlPropertyDecisionNode( |
|||
property, |
|||
splitval, |
|||
tree.Count + toDecode + 1, |
|||
tree.Count + toDecode + 2, |
|||
JxlPredictor.Zero, |
|||
0, |
|||
1)); |
|||
|
|||
toDecode += 2; |
|||
} |
|||
|
|||
ValidateTree(tree); |
|||
} |
|||
|
|||
public static void DecodeTree(Configuration configuration, JxlBitReader reader, Tree tree, int treeSizeLimit) |
|||
{ |
|||
List<byte> treeContextMap = []; |
|||
JxlAnsCode code = new(); |
|||
|
|||
JxlAnsReader.DecodeHistograms(configuration, reader, JxlMaConstants.NumTreeContexts, code, treeContextMap); |
|||
|
|||
if (code.DegenerateSymbols[treeContextMap[(int)JxlMaTreeContext.Property]] > 0) |
|||
{ |
|||
throw new InvalidOperationException("Infinite tree"); |
|||
} |
|||
|
|||
JxlAnsSymbolReader symbolReader = JxlAnsSymbolReader.Create(code, reader); |
|||
DecodeTree(reader, symbolReader, treeContextMap, tree, Math.Min(treeSizeLimit, JxlMaConstants.MaxTreeSize)); |
|||
|
|||
if (!symbolReader.CheckAnsFinalState()) |
|||
{ |
|||
throw new InvalidOperationException("ANS decode final state failed"); |
|||
} |
|||
} |
|||
|
|||
private struct WorkItem(int nodeIndex, int origL, int origU, NextAction action) |
|||
{ |
|||
public int NodeIndex { get; set; } = nodeIndex; |
|||
|
|||
public int OriginalL { get; set; } = origL; |
|||
|
|||
public int OriginalU { get; set; } = origU; |
|||
|
|||
public NextAction Action { get; set; } = action; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue