mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Files implemented: quant_weights.cc, quant_weights.h, modular/encoding/encoding.cc, modular/encoding/encoding.h, ma_common.h, bits_test.ccpull/3153/head
20 changed files with 2017 additions and 15 deletions
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Conditionally invokes the specified method when
|
||||
|
/// appropriate and when it goes out of scope.
|
||||
|
/// </summary>
|
||||
|
internal struct JxlScopeGuard(Action action) : IDisposable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// When true the action will be invoked when Dispose() is called.
|
||||
|
/// </summary>
|
||||
|
private bool isArmed = true; |
||||
|
|
||||
|
public void Disarm() => this.isArmed = false; |
||||
|
|
||||
|
public readonly void Dispose() |
||||
|
{ |
||||
|
if (this.isArmed) |
||||
|
{ |
||||
|
action(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.InteropServices; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.ContextPrediction; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Transforms; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding; |
||||
|
|
||||
|
internal sealed class JxlGroupHeader : IJxlFields |
||||
|
{ |
||||
|
private bool useGlobalTree; |
||||
|
|
||||
|
public JxlGroupHeader() => JxlBundle.Init(this); |
||||
|
|
||||
|
public bool UseGlobalTree |
||||
|
{ |
||||
|
get => this.useGlobalTree; |
||||
|
set => this.useGlobalTree = value; |
||||
|
} |
||||
|
|
||||
|
internal JxlModularHeader WeightedHeader { get; set; } = new(); |
||||
|
|
||||
|
internal List<JxlTransform> Transforms { get; private set; } = []; |
||||
|
|
||||
|
public bool Visit(JxlVisitor visitor) |
||||
|
{ |
||||
|
if (!visitor.Boolean(false, ref this.useGlobalTree)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (!visitor.VisitNested(this.WeightedHeader)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
uint numTransforms = (uint)this.Transforms.Count; |
||||
|
|
||||
|
_ = visitor.U32( |
||||
|
JxlFieldExpressions.Value(0), |
||||
|
JxlFieldExpressions.Value(1), |
||||
|
JxlFieldExpressions.BitsOffset(4, 2), |
||||
|
JxlFieldExpressions.BitsOffset(8, 18), |
||||
|
0, |
||||
|
ref numTransforms); |
||||
|
|
||||
|
if (visitor.IsReading) |
||||
|
{ |
||||
|
this.Transforms = new List<JxlTransform>((int)numTransforms); |
||||
|
} |
||||
|
|
||||
|
Span<JxlTransform> sp = CollectionsMarshal.AsSpan(this.Transforms); |
||||
|
|
||||
|
for (int i = 0; i < numTransforms; i++) |
||||
|
{ |
||||
|
if (!visitor.VisitNested(sp[i])) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding; |
||||
|
|
||||
|
internal static class JxlMaConstants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Total number of MA tree contexts.
|
||||
|
/// </summary>
|
||||
|
public const int NumTreeContexts = 6; |
||||
|
|
||||
|
public const int MaxTreeSize = 1 << 22; |
||||
|
|
||||
|
public const int PropertyRangeFast = 512 << 4; |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding; |
||||
|
|
||||
|
internal enum JxlMaTreeContext |
||||
|
{ |
||||
|
SplitValue, |
||||
|
|
||||
|
Property, |
||||
|
|
||||
|
Predictor, |
||||
|
|
||||
|
Offset, |
||||
|
|
||||
|
MultiplierLog, |
||||
|
|
||||
|
MultiplierBits |
||||
|
} |
||||
@ -0,0 +1,866 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.ContextPrediction; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Transforms; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Primitives; |
||||
|
using FlatTree = System.Collections.Generic.List<SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.ContextPrediction.JxlFlatDecisionNode>; |
||||
|
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 JxlModularEncoding |
||||
|
{ |
||||
|
public static bool TreeToLookupTable<T>(FlatTree tree, JxlTreeLut<T> lut) |
||||
|
{ |
||||
|
bool hasOffsets = lut.Offsets.Length > 0; |
||||
|
bool hasMultipliers = lut.Multipliers.Length > 0; |
||||
|
|
||||
|
Stack<TreeRange> ranges = []; |
||||
|
ranges.Push(new(-JxlMaConstants.PropertyRangeFast - 1, JxlMaConstants.PropertyRangeFast - 1, 0)); |
||||
|
while (ranges.Count > 0) |
||||
|
{ |
||||
|
TreeRange cur = ranges.Peek(); |
||||
|
_ = ranges.Pop(); |
||||
|
|
||||
|
if (cur.Begin < -JxlMaConstants.PropertyRangeFast - 1 || cur.begin >= JxlMaConstants.PropertyRangeFast - 1 || cur.end > JxlMaConstants.PropertyRangeFast - 1) |
||||
|
{ |
||||
|
// Tree is outside the allowed range, exit.
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
JxlMaNode node = tree[cur.Pos]; |
||||
|
|
||||
|
// Leaf.
|
||||
|
if (node.Property0 == -1) |
||||
|
{ |
||||
|
if (node.PredictorOffset is < sbyte.MinValue or > sbyte.MaxValue) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (node.Multiplier is < sbyte.MinValue or > sbyte.MaxValue) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (!hasMultipliers && node.Multiplier != 1) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (!hasOffsets && node.PredictorOffset != 0) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
for (int i = cur.Begin + 1; i < cur.End + 1; i++) |
||||
|
{ |
||||
|
lut.ContextLookup[i + JxlMaConstants.PropertyRangeFast] = node.ChildID; |
||||
|
|
||||
|
if (hasMultipliers) |
||||
|
{ |
||||
|
lut.Multipliers[i + JxlMaConstants.PropertyRangeFast] = node.Multiplier; |
||||
|
} |
||||
|
|
||||
|
if (hasOffsets) |
||||
|
{ |
||||
|
lut.Offsets[i + JxlMaConstants.PropertyRangeFast] = node.PredictorOffset; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (node.Properties[0] >= NumStaticProperties) |
||||
|
{ |
||||
|
ranges.Push(new(node.SplitValues[0], cur.End, node.ChildID)); |
||||
|
ranges.Push(new(node.SplitValue0, node.SplitValues[0], node.ChildID + 1)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ranges.Push(new(node.SplitValue0, cur.End, node.ChildID)); |
||||
|
} |
||||
|
|
||||
|
// <= side
|
||||
|
if (node.Properties[1] >= NumStaticProperties) |
||||
|
{ |
||||
|
ranges.Push( |
||||
|
new(node.SplitValues[1], node.SplitValue0, node.ChildID + 2)); |
||||
|
ranges.Push( |
||||
|
new(cur.Begin, node.SplitValues[1], node.ChildID + 3)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ranges.Push(new(cur.Begin, node.SplitValue0, node.ChildID + 2)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public static FlatTree FilterTree(Tree globalTree, Span<int> staticProps, ref int numProps, ref bool useWp, ref bool wpOnly, ref bool gradientOnly) |
||||
|
{ |
||||
|
numProps = 0; |
||||
|
bool hasWeightedPrediction = false; |
||||
|
bool hasNonWeighted = false; |
||||
|
gradientOnly = true; |
||||
|
|
||||
|
void MarkProperty(int p, ref bool gradientOnly) |
||||
|
{ |
||||
|
if (p == WpProp) |
||||
|
{ |
||||
|
hasWeightedPrediction = true; |
||||
|
} |
||||
|
else if (p >= NumStaticProperties) |
||||
|
{ |
||||
|
hasNonWeighted = true; |
||||
|
} |
||||
|
|
||||
|
if (p >= NumStaticProperties && p != GradientProp) |
||||
|
{ |
||||
|
gradientOnly = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
FlatTree output = []; |
||||
|
Queue<int> nodes = []; |
||||
|
nodes.Enqueue(0); |
||||
|
|
||||
|
while (nodes.Count > 0) |
||||
|
{ |
||||
|
int cur = nodes.Peek(); |
||||
|
_ = nodes.Dequeue(); |
||||
|
|
||||
|
while (globalTree[cur].Property < NumStaticProperties && globalTree[cur].Property != -1) |
||||
|
{ |
||||
|
if (staticProps[globalTree[cur].Property] > globalTree[cur].SplitValue) |
||||
|
{ |
||||
|
cur = globalTree[cur].LeftChild; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
cur = globalTree[cur].RightChild; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
JxlFlatDecisionNode flat = default; |
||||
|
|
||||
|
if (globalTree[cur].Property == -1) |
||||
|
{ |
||||
|
flat.Property0 = -1; |
||||
|
flat.ChildID = (uint)globalTree[cur].LeftChild; |
||||
|
flat.Predictor = globalTree[cur].Predictor; |
||||
|
flat.PredictorOffset = (int)globalTree[cur].PredictorOffset; |
||||
|
flat.Multiplier = globalTree[cur].Multiplier; |
||||
|
|
||||
|
gradientOnly &= flat.Predictor == JxlPredictor.Gradient; |
||||
|
|
||||
|
hasWeightedPrediction |= flat.Predictor == JxlPredictor.Weighted; |
||||
|
hasNonWeighted |= flat.Predictor != JxlPredictor.Weighted; |
||||
|
output.Add(flat); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
flat.ChildID = (uint)(output.Count + nodes.Count + 1); |
||||
|
|
||||
|
flat.Property0 = globalTree[cur].Property; |
||||
|
numProps = Math.Max(flat.Property0 + 1, numProps); |
||||
|
flat.SplitValue0 = globalTree[cur].SplitValue; |
||||
|
|
||||
|
for (int i = 0; i < 2; i++) |
||||
|
{ |
||||
|
int currentChild = i == 0 ? globalTree[cur].LeftChild : globalTree[cur].RightChild; |
||||
|
|
||||
|
while (globalTree[currentChild].Property < kNumStaticProperties && globalTree[currentChild].Property != -1) |
||||
|
{ |
||||
|
if (staticProps[globalTree[currentChild].Property] > |
||||
|
globalTree[currentChild].SplitValue) |
||||
|
{ |
||||
|
currentChild = globalTree[currentChild].LeftChild; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
currentChild = globalTree[currentChild].RightChild; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (globalTree[currentChild].Property == -1) |
||||
|
{ |
||||
|
flat.Properties[i] = 0; |
||||
|
flat.SplitValues[i] = 0; |
||||
|
nodes.Enqueue(currentChild); |
||||
|
nodes.Enqueue(currentChild); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
flat.Properties[i] = globalTree[currentChild].Property; |
||||
|
flat.SplitValues[i] = globalTree[currentChild].SplitValue; |
||||
|
nodes.Enqueue(globalTree[currentChild].LeftChild); |
||||
|
nodes.Enqueue(globalTree[currentChild].RightChild); |
||||
|
|
||||
|
numProps = Math.Max(flat.Properties[i] + 1, numProps); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < 2; i++) |
||||
|
{ |
||||
|
short property = flat.Properties[i]; |
||||
|
MarkProperty(property, ref gradientOnly); |
||||
|
} |
||||
|
|
||||
|
MarkProperty(flat.Property0, ref gradientOnly); |
||||
|
|
||||
|
output.Add(flat); |
||||
|
} |
||||
|
|
||||
|
if (numProps > JxlMaConstants.NumTreeContexts) |
||||
|
{ |
||||
|
numProps = (JxlMath.DivCeil(numProps - JxlMaConstants.NumTreeContexts, kExtraPropsPerChannel) * kExtraPropsPerChannel) + JxlMaConstants.NumTreeContexts; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
numProps = JxlMaConstants.NumTreeContexts; |
||||
|
} |
||||
|
|
||||
|
useWp = hasWeightedPrediction; |
||||
|
wpOnly = hasWeightedPrediction && !hasNonWeighted; |
||||
|
|
||||
|
return output; |
||||
|
} |
||||
|
|
||||
|
public static void DecodeModularChannelMAANS(Configuration configuration, bool usesLz77, JxlBitReader bitReader, JxlAnsSymbolReader reader, List<byte> contextMap, Tree globalTree, JxlModularHeader wpHeader, int channelIdx, int groupId, JxlTreeLut<byte> treeLookup, JxlModularImage image, ref uint flRun, ref uint flV) |
||||
|
{ |
||||
|
JxlModularChannel channel = image.Channels[channelIdx]; |
||||
|
|
||||
|
InlineArray2<int> staticProps = default; |
||||
|
staticProps[0] = channelIdx; |
||||
|
staticProps[1] = groupId; |
||||
|
|
||||
|
if ((channel.Width & channel.Height) == 0) // equivalent to (channel.Width == 0 || channel.Height == 0)
|
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
bool treeHasWeightedPredictionPropOrPred = false; |
||||
|
bool isWeightedPredictionOnly = false; |
||||
|
bool isGradientOnly = false; |
||||
|
int numProps = 0; |
||||
|
|
||||
|
FlatTree tree = FilterTree(globalTree, staticProps, ref numProps, ref treeHasWeightedPredictionPropOrPred, ref isWeightedPredictionOnly, ref isGradientOnly); |
||||
|
Span<JxlFlatDecisionNode> span = CollectionsMarshal.AsSpan(tree); |
||||
|
|
||||
|
for (int i = 0; i < span.Length; i++) |
||||
|
{ |
||||
|
ref JxlFlatDecisionNode node = ref span[i]; |
||||
|
|
||||
|
if (node.Property0 == -1) |
||||
|
{ |
||||
|
node.ChildID = contextMap[(int)node.ChildID]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int MakePixel(uint v, int multiplier, int offset) |
||||
|
{ |
||||
|
int value = JxlPackSigned.UnpackSigned(v); |
||||
|
return (value * multiplier) + offset; |
||||
|
} |
||||
|
|
||||
|
bool GlobalTreeIsAllGradientNoOp() |
||||
|
{ |
||||
|
foreach (JxlPropertyDecisionNode n in globalTree) |
||||
|
{ |
||||
|
if (n.Property == -1) |
||||
|
{ |
||||
|
if (n.Predictor != JxlPredictor.Gradient || n.PredictorOffset != 0 || n.Multiplier != 1) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
else if (n.Property >= NumStaticProperties) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (tree.Count == 1) |
||||
|
{ |
||||
|
JxlPredictor predictor = tree[0].Predictor; |
||||
|
int offset = tree[0].PredictorOffset; |
||||
|
int multiplier = tree[0].Multiplier; |
||||
|
uint ctx_id = tree[0].ChildID; |
||||
|
|
||||
|
if (predictor == JxlPredictor.Zero) |
||||
|
{ |
||||
|
if (reader.IsSingleValueAndAdvance(ctx_id, channel.Width * channel.Height, out uint value)) |
||||
|
{ |
||||
|
int v = MakePixel(value, multiplier, offset); |
||||
|
|
||||
|
for (int y = 0; y < channel.Height; y++) |
||||
|
{ |
||||
|
Span<int> r = channel.GetRow(y); |
||||
|
r[..channel.Width].Fill(v); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (multiplier == 1 && offset == 0) |
||||
|
{ |
||||
|
for (int y = 0; y < channel.Height; y++) |
||||
|
{ |
||||
|
Span<int> r = channel.GetRow(y); |
||||
|
|
||||
|
for (int x = 0; x < channel.Width; x++) |
||||
|
{ |
||||
|
uint v = reader.ReadHybridUintClusteredInlined(usesLz77, ctx_id, bitReader); |
||||
|
|
||||
|
r[x] = JxlPackSigned.UnpackSigned(v); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
for (int y = 0; y < channel.Height; y++) |
||||
|
{ |
||||
|
Span<int> r = channel.GetRow(y); |
||||
|
|
||||
|
for (int x = 0; x < channel.Width; x++) |
||||
|
{ |
||||
|
uint v = reader.ReadHybridUintClusteredMaybeInlined(usesLz77, ctx_id, bitReader); |
||||
|
|
||||
|
r[x] = MakePixel(v, multiplier, offset); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
else if (usesLz77 && reader.IsHuffRleOnly && GlobalTreeIsAllGradientNoOp()) |
||||
|
{ |
||||
|
int sv = JxlPackSigned.UnpackSigned(flV); |
||||
|
|
||||
|
for (int y = 0; y < channel.Height; y++) |
||||
|
{ |
||||
|
Span<int> r = channel.GetRow(y); |
||||
|
Span<int> rtop = y > 0 ? channel.GetRow(y - 1) : channel.GetRowMinus(y, 1); |
||||
|
Span<int> rtopleft = y > 0 ? channel.GetRowMinus(y - 1, 1) : channel.GetRowMinus(y, 1); |
||||
|
int guess0 = y > 0 ? rtop[0] : 0; |
||||
|
|
||||
|
if (flRun == 0) |
||||
|
{ |
||||
|
reader.ReadHybridUintClusteredHuffRleOnly(ctx_id, bitReader, ref flV, ref flRun); |
||||
|
sv = JxlPackSigned.UnpackSigned(flV); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
flRun--; |
||||
|
} |
||||
|
|
||||
|
r[0] = sv + guess0; |
||||
|
|
||||
|
for (int x = 1; x < channel.Width; x++) |
||||
|
{ |
||||
|
int left = r[x - 1]; |
||||
|
int top = rtop[x]; |
||||
|
int topleft = rtopleft[x]; |
||||
|
int guess = JxlContextPrediction.ClampedGradient(top, left, topleft); |
||||
|
|
||||
|
if (flRun == 0) |
||||
|
{ |
||||
|
reader.ReadHybridUintClusteredHuffRleOnly(ctx_id, bitReader, ref flV, ref flRun); |
||||
|
sv = JxlPackSigned.UnpackSigned(flV); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
flRun--; |
||||
|
} |
||||
|
|
||||
|
r[x] = sv + guess; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
else if (predictor == JxlPredictor.Gradient && offset == 0 && multiplier == 1) |
||||
|
{ |
||||
|
int onerow = channel.Plane.PixelsPerRow; |
||||
|
|
||||
|
for (int y = 0; y < channel.Height; y++) |
||||
|
{ |
||||
|
Span<int> r = channel.GetRow(y); |
||||
|
|
||||
|
for (int x = 0; x < channel.Width; x++) |
||||
|
{ |
||||
|
// Neighbors
|
||||
|
int left = x > 0 ? r[x - 1] : y > 0 ? channel.GetRowMinus(y, x - onerow)[0] : 0; |
||||
|
int top = y > 0 ? channel.GetRowMinus(y, x - onerow)[0] : left; |
||||
|
int topleft = x > 0 && y > 0 ? channel.GetRowMinus(y, x - 1 - onerow)[0] : left; |
||||
|
|
||||
|
int guess = JxlContextPrediction.ClampedGradient(top, left, topleft); |
||||
|
uint v = reader.ReadHybridUintClusteredMaybeInlined>( |
||||
|
usesLz77, |
||||
|
ctx_id, |
||||
|
bitReader); |
||||
|
|
||||
|
r[x] = MakePixel(v, 1, guess); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (isWeightedPredictionOnly) |
||||
|
{ |
||||
|
isWeightedPredictionOnly = TreeToLookupTable(tree, treeLookup); |
||||
|
} |
||||
|
|
||||
|
if (isGradientOnly) |
||||
|
{ |
||||
|
isGradientOnly = TreeToLookupTable(tree, treeLookup); |
||||
|
} |
||||
|
|
||||
|
if (isGradientOnly) |
||||
|
{ |
||||
|
int onerow = channel.Plane.PixelsPerRow; |
||||
|
|
||||
|
for (int y = 0; y < channel.Height; y++) |
||||
|
{ |
||||
|
Span<int> r = channel.GetRow(y); |
||||
|
|
||||
|
for (int x = 0; x < channel.Width; x++) |
||||
|
{ |
||||
|
// Neighbors
|
||||
|
int left = x > 0 ? channel.GetRowPlus(y, x - 1)[0] : y > 0 ? channel.GetRowPlus(y, x - onerow)[0] : 0; |
||||
|
int top = y > 0 ? channel.GetRowPlus(y, x - onerow)[0] : left; |
||||
|
int topleft = x > 0 && y > 0 ? channel.GetRowPlus(y, x - 1 - onerow)[0] : left; |
||||
|
|
||||
|
int guess = JxlContextPrediction.ClampedGradient(top, left, topleft); |
||||
|
int pos = |
||||
|
JxlMaConstants.PropertyRangeFast + |
||||
|
Math.Min( |
||||
|
Math.Max(-JxlMaConstants.PropertyRangeFast, top + left - topleft), |
||||
|
JxlMaConstants.PropertyRangeFast - 1); |
||||
|
|
||||
|
uint ctx_id = treeLookup.ContextLookup[pos]; |
||||
|
uint v = reader.ReadHybridUintClusteredMaybeInlined(usesLz77, ctx_id, br); |
||||
|
r[x] = MakePixel(v, 1, guess); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else if (!usesLz77 && isWeightedPredictionOnly && channel.Width > 8) |
||||
|
{ |
||||
|
JxlModularState wpState = new(wpHeader, channel.Width); |
||||
|
Span<int> properties = [0]; |
||||
|
|
||||
|
for (int y = 0; y < channel.Height; y++) |
||||
|
{ |
||||
|
Span<int> r = channel.GetRow(y); |
||||
|
Span<int> rtop = y > 0 ? channel.GetRow(y - 1) : channel.GetRowMinus(y, 1); |
||||
|
Span<int> rtoptop = y > 1 ? channel.GetRow(y - 2) : rtop; |
||||
|
Span<int> rtopleft = y > 0 ? channel.GetRowMinus(y - 1, 1) : channel.GetRowMinus(y, 1); |
||||
|
Span<int> rtopright = y > 0 ? channel.GetRow(y - 1)[1..] : channel.GetRowMinus(y, 1); |
||||
|
|
||||
|
int x = 0; |
||||
|
{ |
||||
|
int offset = 0; |
||||
|
int left = y > 0 ? rtop[x] : 0; |
||||
|
int toptop = y > 0 ? rtoptop[x] : 0; |
||||
|
int topright = x + 1 < channel.Width && y > 0 ? rtop[x + 1] : left; |
||||
|
|
||||
|
int guess = (int)wpState.Predict(true, x, y, channel.Width, left, left, topright, left, toptop, properties, offset); |
||||
|
int pos = JxlMaConstants.PropertyRangeFast + Math.Clamp(properties[0], -JxlMaConstants.PropertyRangeFast, JxlMaConstants.PropertyRangeFast - 1); |
||||
|
|
||||
|
uint ctx_id = treeLookup.ContextLookup[pos]; |
||||
|
uint v = reader.ReadHybridUintClusteredInlined(usesLz77, ctx_id, bitReader); |
||||
|
|
||||
|
r[x] = MakePixel(v, 1, guess); |
||||
|
wpState.UpdatePredictionErrors(r[x], x, y, channel.Width); |
||||
|
} |
||||
|
|
||||
|
for (x = 1; x + 1 < channel.Width; x++) |
||||
|
{ |
||||
|
int offset = 0; |
||||
|
int guess = (int)wpState.Predict(true, x, y, channel.Width, rtop[x], r[x - 1], rtopright[x], rtopleft[x], rtoptop[x], properties, offset); |
||||
|
int pos = JxlMaConstants.PropertyRangeFast + Math.Clamp(properties[0], -JxlMaConstants.PropertyRangeFast, JxlMaConstants.PropertyRangeFast - 1); |
||||
|
|
||||
|
int ctx_id = treeLookup.ContextLookup[pos]; |
||||
|
uint v = reader.ReadHybridUintClusteredInlined(usesLz77, ctx_id, bitReader); |
||||
|
|
||||
|
r[x] = MakePixel(v, 1, guess); |
||||
|
wpState.UpdatePredictionErrors(r[x], x, y, channel.Width); |
||||
|
} |
||||
|
|
||||
|
{ |
||||
|
int offset = 0; |
||||
|
int guess = (int)wpState.Predict(true, x, y, channel.Width, rtop[x], r[x - 1], rtop[x], rtopleft[x], rtoptop[x], properties, offset); |
||||
|
int pos = JxlMaConstants.PropertyRangeFast + Math.Clamp(properties[0], -JxlMaConstants.PropertyRangeFast, JxlMaConstants.PropertyRangeFast - 1); |
||||
|
|
||||
|
int ctx_id = treeLookup.ContextLookup[pos]; |
||||
|
uint v = reader.ReadHybridUintClusteredInlined(usesLz77, ctx_id, bitReader); |
||||
|
|
||||
|
r[x] = MakePixel(v, 1, guess); |
||||
|
wpState.UpdatePredictionErrors(r[x], x, y, channel.Width); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else if (!treeHasWeightedPredictionPropOrPred) |
||||
|
{ |
||||
|
JxlMaTreeLookup tree_lookup = new(tree); |
||||
|
Span<int> properties = stackalloc int[numProps]; |
||||
|
int onerow = channel.Plane.PixelsPerRow; |
||||
|
|
||||
|
JxlModularChannel references = new(configuration, properties.Length - NumNonrefProperties, channel.Width, 0, 0); |
||||
|
|
||||
|
for (int y = 0; y < channel.Height; y++) |
||||
|
{ |
||||
|
Span<int> p = channel.GetRow(y); |
||||
|
PrecomputeReferences(channel, y, image, channelIdx, references); |
||||
|
InitPropsRow(properties, staticProps, y); |
||||
|
|
||||
|
if (y > 1 && channel.Width > 8 && references.Width == 0) |
||||
|
{ |
||||
|
for (int x = 0; x < 2; x++) |
||||
|
{ |
||||
|
JxlPredictionResult res = JxlContextPrediction.PredictTreeNoWeightedPrediction(properties, channel.Width, ref p[x], onerow, x, y, tree_lookup, references); |
||||
|
uint v = reader.ReadHybridUintClustered(usesLz77, res.Context, bitReader); |
||||
|
p[x] = MakePixel(v, res.Multiplier, res.Guess); |
||||
|
} |
||||
|
|
||||
|
for (int x = 2; x < channel.Width - 2; x++) |
||||
|
{ |
||||
|
JxlPredictionResult res = JxlContextPrediction.PredictTreeNoWeightedPredictionNoEdgeCases(properties, channel.Width, ref p[x], onerow, x, y, tree_lookup, references); |
||||
|
uint v = reader.ReadHybridUintClusteredInlined(usesLz77, res.Context, bitReader); |
||||
|
p[x] = MakePixel(v, res.Multiplier, res.Guess); |
||||
|
} |
||||
|
|
||||
|
for (int x = channel.Width - 2; x < channel.Width; x++) |
||||
|
{ |
||||
|
JxlPredictionResult res = JxlContextPrediction.PredictTreeNoWeightedPrediction(properties, channel.Width, ref p[x], onerow, x, y, tree_lookup, references); |
||||
|
uint v = reader.ReadHybridUintClustered(usesLz77, res.Context, bitReader); |
||||
|
p[x] = MakePixel(v, res.Multiplier, res.Guess); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
for (int x = 0; x < channel.Width; x++) |
||||
|
{ |
||||
|
JxlPredictionResult res = JxlContextPrediction.PredictTreeNoWeightedPrediction(properties, channel.Width, ref p[x], onerow, x, y, tree_lookup, references); |
||||
|
uint v = reader.ReadHybridUintClusteredMaybeInlined(usesLz77, res.Context, bitReader); |
||||
|
p[x] = MakePixel(v, res.Multiplier, res.Guess); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
JxlMaTreeLookup tree_lookup = new(tree); |
||||
|
Span<int> properties = stackalloc int[numProps]; |
||||
|
int onerow = channel.Plane.PixelsPerRow; |
||||
|
|
||||
|
JxlModularChannel references = new(configuration, properties.Length - NumNonrefProperties, channel.Width, 0, 0); |
||||
|
JxlModularState wpState = new(wpHeader, channel.Width); |
||||
|
|
||||
|
for (int y = 0; y < channel.Height; y++) |
||||
|
{ |
||||
|
Span<int> p = channel.GetRow(y); |
||||
|
InitPropsRow(properties, staticProps, y); |
||||
|
PrecomputeReferences(channel, y, image, channelIdx, references); |
||||
|
|
||||
|
if (!usesLz77 && y > 1 && channel.Width > 8 && references.Width == 0) |
||||
|
{ |
||||
|
for (int x = 0; x < 2; x++) |
||||
|
{ |
||||
|
JxlPredictionResult res = JxlContextPrediction.PredictTreeWeightedPrediction(properties, channel.Width, ref p[x], onerow, x, y, tree_lookup, references, wpState); |
||||
|
uint v = reader.ReadHybridUintClustered(usesLz77, res.Context, bitReader); |
||||
|
|
||||
|
p[x] = MakePixel(v, res.Multiplier, res.Guess); |
||||
|
wpState.UpdatePredictionErrors(p[x], x, y, channel.Width); |
||||
|
} |
||||
|
|
||||
|
for (int x = 2; x < channel.Width - 2; x++) |
||||
|
{ |
||||
|
JxlPredictionResult res = JxlContextPrediction.PredictTreeWeightedPredictionNoEdgeCases(properties, channel.Width, ref p[x], onerow, x, y, tree_lookup, references, wpState); |
||||
|
uint v = reader.ReadHybridUintClusteredInlined(usesLz77, res.Context, bitReader); |
||||
|
|
||||
|
p[x] = MakePixel(v, res.Multiplier, res.Guess); |
||||
|
wpState.UpdatePredictionErrors(p[x], x, y, channel.Width); |
||||
|
} |
||||
|
|
||||
|
for (int x = channel.Width - 2; x < channel.Width; x++) |
||||
|
{ |
||||
|
JxlPredictionResult res = JxlContextPrediction.PredictTreeWeightedPrediction(properties, channel.Width, ref p[x], onerow, x, y, tree_lookup, references, wpState); |
||||
|
uint v = reader.ReadHybridUintClustered(usesLz77, res.Context, bitReader); |
||||
|
|
||||
|
p[x] = MakePixel(v, res.Multiplier, res.Guess); |
||||
|
wpState.UpdatePredictionErrors(p[x], x, y, channel.Width); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
for (int x = 0; x < channel.Width; x++) |
||||
|
{ |
||||
|
JxlPredictionResult res = JxlContextPrediction.PredictTreeWeightedPrediction(properties, channel.Width, ref p[x], onerow, x, y, tree_lookup, references, wpState); |
||||
|
uint v = reader.ReadHybridUintClustered(usesLz77, res.Context, bitReader); |
||||
|
|
||||
|
p[x] = MakePixel(v, res.Multiplier, res.Guess); |
||||
|
wpState.UpdatePredictionErrors(p[x], x, y, channel.Width); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void DecodeModularChannelMAANS(JxlBitReader bitReader, JxlAnsSymbolReader reader, List<byte> contextMap, Tree globalTree, JxlModularHeader wpHeader, int channelIdx, int groupId, JxlTreeLut<byte> treeLut, JxlModularImage image, ref uint flRun, ref uint flV) |
||||
|
=> DecodeModularChannelMAANS(reader.UsesLz77, bitReader, reader, contextMap, globalTree, wpHeader, channelIdx, groupId, treeLut, image, ref flRun, ref flV); |
||||
|
|
||||
|
public static void ValidateChannelDimensions(JxlModularImage image, JxlModularOptions options) |
||||
|
{ |
||||
|
int nbChannels = image.Channels.Count; |
||||
|
|
||||
|
foreach (bool isDc in (bool[])[true, false]) |
||||
|
{ |
||||
|
int groupDimensions = options.group_dim * (isDc ? JxlFrameDimensions.BlockDimensions : 1); |
||||
|
int c = image.MetaChannels; |
||||
|
|
||||
|
for (; c < nbChannels; c++) |
||||
|
{ |
||||
|
JxlModularChannel ch = image.Channels[c]; |
||||
|
if (ch.Width > options.group_dim || ch.Height > options.group_dim) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; c < nbChannels; c++) |
||||
|
{ |
||||
|
JxlModularChannel ch = image.Channels[c]; |
||||
|
if (ch.Width == 0 || ch.Height == 0) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
bool isDcChannel = Math.Min(ch.HorizontalShift, ch.VerticalShift) >= 3; |
||||
|
if (isDcChannel != isDc) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
int tileDimensions = groupDimensions >> Math.Max(ch.HorizontalShift, ch.VerticalShift); |
||||
|
if (tileDimensions == 0) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Inconsistent transforms"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static bool ModularDecode(Configuration configuration, JxlBitReader br, JxlModularImage image, JxlGroupHeader header, int groupId, JxlModularOptions options, Tree globalTree, JxlAnsCode globalCode, List<byte> globalContextMap, bool allowTruncatedGroup) |
||||
|
{ |
||||
|
if (image.Channels.Count == 0) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (!JxlBundle.Read(br, header) && !allowTruncatedGroup) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Could not read a bundle"); |
||||
|
} |
||||
|
|
||||
|
image.Transforms = header.Transforms; |
||||
|
|
||||
|
foreach (JxlTransform transform in image.Transforms) |
||||
|
{ |
||||
|
transform.MetaApply(image); |
||||
|
} |
||||
|
|
||||
|
if (image.IsInvalid) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Corrupt file. Aborting."); |
||||
|
} |
||||
|
|
||||
|
ValidateChannelDimensions(image, options); |
||||
|
|
||||
|
int numberOfChannels = image.Channels.Count; |
||||
|
int numChannels = 0; |
||||
|
int distanceMultiplier = 0; |
||||
|
|
||||
|
for (int i = 0; i < numberOfChannels; i++) |
||||
|
{ |
||||
|
JxlModularChannel channel = image.Channels[i]; |
||||
|
if (i >= image.MetaChannels && (channel.Width > options.MaxChannelSize || channel.Height > options.MaxChannelSize)) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (channel.Width == 0 || channel.Height == 0) |
||||
|
{ |
||||
|
continue; // skip empty channels
|
||||
|
} |
||||
|
|
||||
|
if (channel.Width > distanceMultiplier) |
||||
|
{ |
||||
|
distanceMultiplier = channel.Width; |
||||
|
} |
||||
|
|
||||
|
numChannels++; |
||||
|
} |
||||
|
|
||||
|
if (numChannels == 0) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
int nextChannel = 0; |
||||
|
using JxlScopeGuard clearGuard = new(() => |
||||
|
{ |
||||
|
for (int c = nextChannel; c < image.Channels.Count; c++) |
||||
|
{ |
||||
|
image.Channels[c].Plane.Clear(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (allowTruncatedGroup) |
||||
|
{ |
||||
|
clearGuard.Disarm(); |
||||
|
} |
||||
|
|
||||
|
Tree treeStorage = []; |
||||
|
List<byte> contextMapStorage = []; |
||||
|
JxlAnsCode codeStorage = new(); |
||||
|
|
||||
|
Tree tree = treeStorage; |
||||
|
JxlAnsCode code = codeStorage; |
||||
|
List<byte> contextMap = contextMapStorage; |
||||
|
|
||||
|
if (!header.UseGlobalTree) |
||||
|
{ |
||||
|
ulong maxTreeSize = 1024; |
||||
|
|
||||
|
for (int i = 0; i < numberOfChannels; i++) |
||||
|
{ |
||||
|
JxlModularChannel channel = image.Channels[i]; |
||||
|
if (i >= image.MetaChannels && (channel.Width > options.MaxChannelSize || channel.Height > options.MaxChannelSize)) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
ulong pixels = (ulong)channel.Width * (ulong)channel.Height; |
||||
|
maxTreeSize += pixels; |
||||
|
} |
||||
|
|
||||
|
maxTreeSize = Math.Min(1uL << 20, maxTreeSize); |
||||
|
DecodeTree(configuration, br, treeStorage, maxTreeSize)); |
||||
|
DecodeHistograms( |
||||
|
configuration, |
||||
|
br, |
||||
|
(treeStorage.Count + 1) / 2, |
||||
|
codeStorage, |
||||
|
contextMapStorage); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (globalTree.Count == 0) |
||||
|
{ |
||||
|
throw new InvalidOperationException("No global tree available but one was requested"); |
||||
|
} |
||||
|
|
||||
|
tree = globalTree; |
||||
|
code = globalCode; |
||||
|
contextMap = globalContextMap; |
||||
|
} |
||||
|
|
||||
|
JxlAnsSymbolReader reader = new(code, br, distanceMultiplier); |
||||
|
JxlTreeLut<byte> treeLut = new(false, false); |
||||
|
|
||||
|
uint flRun = 0; |
||||
|
uint flV = 0; |
||||
|
|
||||
|
for (; nextChannel < numberOfChannels; nextChannel++) |
||||
|
{ |
||||
|
JxlModularChannel channel = image.Channels[nextChannel]; |
||||
|
if (nextChannel >= image.MetaChannels && |
||||
|
(channel.Width > options.MaxChannelSize || |
||||
|
channel.Height > options.MaxChannelSize)) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (channel.Width == 0 || channel.Height == 0) |
||||
|
{ |
||||
|
continue; // skip empty channels
|
||||
|
} |
||||
|
|
||||
|
DecodeModularChannelMAANS(br, reader, contextMap, tree, header.WeightedHeader, nextChannel, groupId, treeLut, image, ref flRun, ref flV); |
||||
|
} |
||||
|
|
||||
|
clearGuard.Disarm(); |
||||
|
|
||||
|
if (!reader.CheckAnsFinalState()) |
||||
|
{ |
||||
|
throw new InvalidOperationException("ANS decode final state failed"); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public static bool ModularGenericDecompress(Configuration configuration. JxlBitReader br, JxlModularImage image, JxlGroupHeader header, int groupId, JxlModularOptions options, bool undoTransforms, Tree tree, JxlAnsCode code, List<byte> contextMap, bool allowTruncatedGroup) |
||||
|
{ |
||||
|
List<Size> reqSizes = new(capacity: image.Channels.Count); |
||||
|
|
||||
|
foreach (JxlModularChannel c in image.Channels) |
||||
|
{ |
||||
|
reqSizes.Add(new(c.Width, c.Height)); |
||||
|
} |
||||
|
|
||||
|
header ??= new JxlGroupHeader(); |
||||
|
|
||||
|
bool decStatus = ModularDecode(configuration, br, image, header, groupId, options, tree, code, contextMap, allowTruncatedGroup); |
||||
|
|
||||
|
if (!allowTruncatedGroup) |
||||
|
{ |
||||
|
if (!decStatus) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (undoTransforms) |
||||
|
{ |
||||
|
image.UndoTransforms(header.WeightedHeader); |
||||
|
} |
||||
|
|
||||
|
if (image.IsInvalid) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Corrupt file. Aborting"); |
||||
|
} |
||||
|
|
||||
|
if (undoTransforms) |
||||
|
{ |
||||
|
if (image.Channels.Count != reqSizes.Count) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
for (int c = 0; c < reqSizes.Count; c++) |
||||
|
{ |
||||
|
if (reqSizes[c].Width != image.Channels[c].Width || reqSizes[c].Height != image.Channels[c].Height) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private readonly record struct TreeRange(int Begin, int End, int Pos); |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.ContextPrediction; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding; |
||||
|
|
||||
|
internal struct JxlPropertyDecisionNode |
||||
|
{ |
||||
|
public int SplitValue; |
||||
|
public int Property; |
||||
|
public int LeftChild; |
||||
|
public int RightChild; |
||||
|
public JxlPredictor Predictor; |
||||
|
public long PredictorOffset; |
||||
|
public int Multiplier; |
||||
|
|
||||
|
public JxlPropertyDecisionNode(int property, int splitValue, int leftChild, int rightChild, JxlPredictor predictor, long predictorOffset, int multiplier) |
||||
|
{ |
||||
|
this.SplitValue = splitValue; |
||||
|
this.Property = property; |
||||
|
this.LeftChild = leftChild; |
||||
|
this.RightChild = rightChild; |
||||
|
this.Predictor = predictor; |
||||
|
this.PredictorOffset = predictorOffset; |
||||
|
this.Multiplier = multiplier; |
||||
|
} |
||||
|
|
||||
|
public JxlPropertyDecisionNode() |
||||
|
: this(0, -1, 0, 0, JxlPredictor.Zero, 0, 1) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public static JxlPropertyDecisionNode Leaf(JxlPredictor predictor, long offset = 0, int multiplier = 1) |
||||
|
=> new(-1, 0, 0, 0, predictor, offset, multiplier); |
||||
|
|
||||
|
public static JxlPropertyDecisionNode Split(int p, int splitValue, int leftChild, int rightChild = -1) |
||||
|
{ |
||||
|
if (rightChild == -1) |
||||
|
{ |
||||
|
rightChild = leftChild + 1; |
||||
|
} |
||||
|
|
||||
|
return new(p, splitValue, leftChild, rightChild, JxlPredictor.Zero, 0, 1); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding; |
||||
|
|
||||
|
internal sealed class JxlTreeLut<T>(bool hasOffsets, bool hasMultipliers) |
||||
|
{ |
||||
|
public T[] ContextLookup { get; } = new T[2 * JxlMaConstants.PropertyRangeFast]; |
||||
|
|
||||
|
public byte[] Offsets { get; } = new byte[hasOffsets ? (2 * JxlMaConstants.PropertyRangeFast) : 0]; |
||||
|
|
||||
|
public byte[] Multipliers { get; } = new byte[hasMultipliers ? (2 * JxlMaConstants.PropertyRangeFast) : 0]; |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Jxl.Processing; |
||||
|
|
||||
|
public class BitsTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void TestNumZeroBits() |
||||
|
{ |
||||
|
Assert.Equal(32u, JxlMath.Num0BitsAboveMS1Bit(0u)); |
||||
|
Assert.Equal(64u, JxlMath.Num0BitsAboveMS1Bit(0uL)); |
||||
|
Assert.Equal(32u, JxlMath.Num0BitsBelowLS1Bit(0u)); |
||||
|
Assert.Equal(64u, JxlMath.Num0BitsBelowLS1Bit(0uL)); |
||||
|
|
||||
|
Assert.Equal(31u, JxlMath.Num0BitsAboveMS1Bit(1u)); |
||||
|
Assert.Equal(30u, JxlMath.Num0BitsAboveMS1Bit(2u)); |
||||
|
Assert.Equal(63u, JxlMath.Num0BitsAboveMS1Bit(1uL)); |
||||
|
Assert.Equal(62u, JxlMath.Num0BitsAboveMS1Bit(2uL)); |
||||
|
|
||||
|
Assert.Equal(0u, JxlMath.Num0BitsBelowLS1Bit(1u)); |
||||
|
Assert.Equal(0u, JxlMath.Num0BitsBelowLS1Bit(1uL)); |
||||
|
Assert.Equal(1u, JxlMath.Num0BitsBelowLS1Bit(2u)); |
||||
|
Assert.Equal(1u, JxlMath.Num0BitsBelowLS1Bit(2uL)); |
||||
|
|
||||
|
Assert.Equal(0u, JxlMath.Num0BitsAboveMS1Bit(0x80000000u)); |
||||
|
Assert.Equal(0u, JxlMath.Num0BitsAboveMS1Bit(0x8000000000000000uL)); |
||||
|
Assert.Equal(31u, JxlMath.Num0BitsBelowLS1Bit(0x80000000u)); |
||||
|
Assert.Equal(63u, JxlMath.Num0BitsBelowLS1Bit(0x8000000000000000uL)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void TestFloorLog2() |
||||
|
{ |
||||
|
Span<int> expected = [0, 1, 1, 2, 2, 2, 2]; |
||||
|
|
||||
|
for (int i = 1; i <= 7; ++i) |
||||
|
{ |
||||
|
Assert.Equal(expected[i - 1], JxlMath.FloorLog2Nonzero(i)); |
||||
|
Assert.Equal((ulong)expected[i - 1], JxlMath.FloorLog2Nonzero((ulong)i)); |
||||
|
} |
||||
|
|
||||
|
Assert.Equal(11u, JxlMath.FloorLog2Nonzero(0x00000fffu)); // 4095
|
||||
|
Assert.Equal(12u, JxlMath.FloorLog2Nonzero(0x00001000u)); // 4096
|
||||
|
Assert.Equal(12u, JxlMath.FloorLog2Nonzero(0x00001001u)); // 4097
|
||||
|
|
||||
|
Assert.Equal(31u, JxlMath.FloorLog2Nonzero(0x80000000u)); |
||||
|
Assert.Equal(31u, JxlMath.FloorLog2Nonzero(0x80000001u)); |
||||
|
Assert.Equal(31u, JxlMath.FloorLog2Nonzero(0xFFFFFFFFu)); |
||||
|
|
||||
|
Assert.Equal(31u, JxlMath.FloorLog2Nonzero(0x80000000uL)); |
||||
|
Assert.Equal(31u, JxlMath.FloorLog2Nonzero(0x80000001uL)); |
||||
|
Assert.Equal(31u, JxlMath.FloorLog2Nonzero(0xFFFFFFFFuL)); |
||||
|
|
||||
|
Assert.Equal(63u, JxlMath.FloorLog2Nonzero(0x8000000000000000uL)); |
||||
|
Assert.Equal(63u, JxlMath.FloorLog2Nonzero(0x8000000000000001uL)); |
||||
|
Assert.Equal(63u, JxlMath.FloorLog2Nonzero(0xFFFFFFFFFFFFFFFFuL)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void TestCeilLog2() |
||||
|
{ |
||||
|
Span<int> expected = [0, 1, 2, 2, 3, 3, 3]; |
||||
|
|
||||
|
for (int i = 1; i <= 7; ++i) |
||||
|
{ |
||||
|
Assert.Equal(expected[i - 1], JxlMath.CeilLog2Nonzero(i)); |
||||
|
Assert.Equal((ulong)expected[i - 1], JxlMath.CeilLog2Nonzero((ulong)i)); |
||||
|
} |
||||
|
|
||||
|
Assert.Equal(12u, JxlMath.CeilLog2Nonzero(0x00000fffu)); // 4095
|
||||
|
Assert.Equal(12u, JxlMath.CeilLog2Nonzero(0x00001000u)); // 4096
|
||||
|
Assert.Equal(13u, JxlMath.CeilLog2Nonzero(0x00001001u)); // 4097
|
||||
|
|
||||
|
Assert.Equal(31u, JxlMath.CeilLog2Nonzero(0x80000000u)); |
||||
|
Assert.Equal(32u, JxlMath.CeilLog2Nonzero(0x80000001u)); |
||||
|
Assert.Equal(32u, JxlMath.CeilLog2Nonzero(0xFFFFFFFFu)); |
||||
|
|
||||
|
Assert.Equal(31u, JxlMath.CeilLog2Nonzero(0x80000000uL)); |
||||
|
Assert.Equal(32u, JxlMath.CeilLog2Nonzero(0x80000001uL)); |
||||
|
Assert.Equal(32u, JxlMath.CeilLog2Nonzero(0xFFFFFFFFuL)); |
||||
|
|
||||
|
Assert.Equal(63u, JxlMath.CeilLog2Nonzero(0x8000000000000000uL)); |
||||
|
Assert.Equal(64u, JxlMath.CeilLog2Nonzero(0x8000000000000001uL)); |
||||
|
Assert.Equal(64u, JxlMath.CeilLog2Nonzero(0xFFFFFFFFFFFFFFFFuL)); |
||||
|
} |
||||
|
} |
||||
@ -1,5 +1,5 @@ |
|||||
# JPEG XL Tests |
# JPEG XL Tests |
||||
|
|
||||
### Preprocessor directives |
### Preprocessor directives |
||||
Enable a ALLOW_JPEGXL_SLOW_TESTS preprocessor directive to allow |
Enable a `ALLOW_JPEGXL_SLOW_TESTS` preprocessor directive to allow |
||||
slower tests that test the library more extensively. |
slower tests that test the library more extensively. |
||||
|
|||||
@ -0,0 +1,19 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Cms; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Jxl.TestUtils; |
||||
|
|
||||
|
internal struct ColorEncodingDescriptor |
||||
|
{ |
||||
|
public JxlColorSpace ColorSpace { get; set; } |
||||
|
|
||||
|
public JxlWhitePoint WhitePoint { get; set; } |
||||
|
|
||||
|
public JxlPrimaries Primaries { get; set; } |
||||
|
|
||||
|
public JxlTransferFunction TransferFunction { get; set; } |
||||
|
|
||||
|
public JxlRenderingIntent RenderingIntent { get; set; } |
||||
|
} |
||||
Loading…
Reference in new issue