mirror of https://github.com/SixLabors/ImageSharp
2 changed files with 423 additions and 1 deletions
@ -0,0 +1,422 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.AcStrategy; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Dct; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.Ans; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.AuxiliaryOutput; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder; |
|||
|
|||
internal static class JxlCoefficientOrderEncoder |
|||
{ |
|||
private struct PosAndCount |
|||
{ |
|||
public uint Pos; |
|||
public ulong CountAndIdx; |
|||
} |
|||
|
|||
private static (uint UsedOrders, uint CustomizeOrders) ComputeUsedOrders(JxlSpeedTier speed, JxlAcStrategyImage acStrategy, Rectangle rect) |
|||
{ |
|||
if (speed >= JxlSpeedTier.Falcon) |
|||
{ |
|||
return (1, 1); |
|||
} |
|||
|
|||
uint ret = 0; |
|||
uint retCustomize = 0; |
|||
int xSizeBlocks = rect.Width; |
|||
int ySizeBlocks = rect.Height; |
|||
|
|||
for (int by = 0; by < ySizeBlocks; by++) |
|||
{ |
|||
JxlAcStrategyRow acsRow = acStrategy.GetRow(rect, by); |
|||
|
|||
for (int bx = 0; bx < xSizeBlocks; bx++) |
|||
{ |
|||
byte ord = JxlCoefficientOrder.StrategyOrder[acsRow[bx].RawStrategy]; |
|||
|
|||
ret |= 1u << ord; |
|||
|
|||
if (ord > 6) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
retCustomize |= 1u << ord; |
|||
} |
|||
} |
|||
|
|||
if (acStrategy.XSize < 5 && acStrategy.YSize < 5) |
|||
{ |
|||
return (ret, 0); |
|||
} |
|||
|
|||
return (ret, retCustomize); |
|||
} |
|||
|
|||
private static bool ComputeCoeffOrder(JxlSpeedTier speed, IJxlDctAcImage acImage, JxlAcStrategyImage acStrategy, JxlFrameDimensions frameDim, ref uint allUsedOrders, uint prevUsedAcs, uint currentUsedAcs, uint currentUsedOrders, Span<int> order) |
|||
{ |
|||
int[] numZeros = new int[JxlCoefficientOrder.CoefficientOrderMaxSize]; |
|||
double blockFraction = 1.0; |
|||
|
|||
if (speed >= JxlSpeedTier.Squirrel && currentUsedOrders == 1) |
|||
{ |
|||
blockFraction = 0.5; |
|||
} |
|||
|
|||
if (currentUsedOrders != 0) |
|||
{ |
|||
ulong threshold = uint.MaxValue * (ulong)blockFraction; |
|||
|
|||
ulong s0 = 0x94D049BB133111EBUL; |
|||
ulong s1 = 0xBF58476D1CE4E5B9UL; |
|||
|
|||
bool UseSample() |
|||
{ |
|||
ulong s = s0; |
|||
ulong s0Local = s1; |
|||
ulong bits = s + s0Local; |
|||
|
|||
s0 = s0Local; |
|||
|
|||
s ^= s << 23; |
|||
s ^= s0Local ^ (s >> 18) ^ (s0Local >> 5); |
|||
s1 = s; |
|||
|
|||
return (bits >> 32) <= threshold; |
|||
} |
|||
|
|||
for (int groupIndex = 0; groupIndex < frameDim.NumGroups; ++groupIndex) |
|||
{ |
|||
int gx = groupIndex % frameDim.XSizeGroups; |
|||
int gy = groupIndex / frameDim.XSizeGroups; |
|||
|
|||
Rectangle rect = RectangleUtils.CreateRectangle( |
|||
gx * JxlFrameDimensions.GroupDimensionsInBlocks, |
|||
gy * JxlFrameDimensions.GroupDimensionsInBlocks, |
|||
JxlFrameDimensions.GroupDimensionsInBlocks, |
|||
JxlFrameDimensions.GroupDimensionsInBlocks, |
|||
frameDim.XSizeBlocks, |
|||
frameDim.YSizeBlocks); |
|||
|
|||
int acOffset = 0; |
|||
JxlDctAcType type = acImage.Type; |
|||
|
|||
for (int by = 0; by < rect.Height; ++by) |
|||
{ |
|||
JxlAcStrategyRow acsRow = acStrategy.GetRow(rect, by); |
|||
|
|||
for (int bx = 0; bx < rect.Width; ++bx) |
|||
{ |
|||
JxlAcStrategy acs = acsRow[bx]; |
|||
|
|||
if (!acs.IsFirstBlock) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (!UseSample()) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
int size = JxlFrameDimensions.DctBlockSize << acs.Log2CoveredBlocks; |
|||
|
|||
for (int c = 0; c < 3; ++c) |
|||
{ |
|||
int orderOffset = JxlCoefficientOrder.CoeffOrderOffset(JxlCoefficientOrder.StrategyOrder[acs.RawStrategy], c); |
|||
|
|||
if (type == JxlDctAcType.Ac16) |
|||
{ |
|||
for (int k = 0; k < size; ++k) |
|||
{ |
|||
bool isZero = acImage.GetPlaneRow(c, groupIndex, 0).Pointer16[acOffset + k] == 0; |
|||
if (isZero) |
|||
{ |
|||
++numZeros[orderOffset + k]; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int k = 0; k < size; ++k) |
|||
{ |
|||
bool isZero = acImage.GetPlaneRow(c, groupIndex, 0).Pointer32[acOffset + k] == 0; |
|||
if (isZero) |
|||
{ |
|||
++numZeros[orderOffset + k]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
int cx = acs.CoveredBlocksX; |
|||
int cy = acs.CoveredBlocksY; |
|||
|
|||
JxlForwardCoefficientOrder.CoefficientLayout(ref cy, ref cx); |
|||
|
|||
for (int iy = 0; iy < cy; ++iy) |
|||
{ |
|||
for (int ix = 0; ix < cx; ++ix) |
|||
{ |
|||
numZeros[orderOffset + (iy * JxlFrameDimensions.BlockDimensions * cx) + ix] = -1; |
|||
} |
|||
} |
|||
} |
|||
|
|||
acOffset += size; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
PosAndCount[] posAndVal = new PosAndCount[JxlAcStrategy.MaximumCoefficientArea]; |
|||
int[] naturalOrderBuffer = []; |
|||
|
|||
ushort computed = 0; |
|||
|
|||
for (byte o = 0; o < JxlAcStrategy.NumberOfValidStrategies; ++o) |
|||
{ |
|||
byte ord = JxlCoefficientOrder.StrategyOrder[o]; |
|||
|
|||
if ((computed & (1 << ord)) != 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
computed |= (ushort)(1 << ord); |
|||
|
|||
JxlAcStrategy acs = new(o); |
|||
|
|||
int sz = JxlFrameDimensions.DctBlockSize * acs.CoveredBlocksX * acs.CoveredBlocksY; |
|||
|
|||
if (sz > (1 << 16)) |
|||
{ |
|||
throw new InvalidOperationException("Inavlid size"); |
|||
} |
|||
|
|||
if (((1u << ord) & ~currentUsedAcs) != 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (((1u << ord) & prevUsedAcs) != 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (((1u << ord) & allUsedOrders) != 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (naturalOrderBuffer.Length < sz) |
|||
{ |
|||
// Not sure how large can sz get...
|
|||
naturalOrderBuffer = new int[sz]; |
|||
} |
|||
|
|||
acs.ComputeNaturalCoefficientOrder(naturalOrderBuffer); |
|||
|
|||
if (((1u << ord) & ~currentUsedOrders) != 0) |
|||
{ |
|||
for (int c = 0; c < 3; ++c) |
|||
{ |
|||
int offset = JxlCoefficientOrder.CoeffOrderOffset(ord, c); |
|||
|
|||
if (JxlCoefficientOrder.CoeffOrderOffset(ord, c + 1) - offset != sz) |
|||
{ |
|||
throw new InvalidOperationException("Invalid coefficient order"); |
|||
} |
|||
|
|||
naturalOrderBuffer.AsSpan(0, sz).CopyTo(order.Slice(offset, sz)); |
|||
} |
|||
|
|||
continue; |
|||
} |
|||
|
|||
bool isNonDefault = false; |
|||
|
|||
for (byte c = 0; c < 3; ++c) |
|||
{ |
|||
int offset = JxlCoefficientOrder.CoeffOrderOffset(ord, c); |
|||
|
|||
if (JxlCoefficientOrder.CoeffOrderOffset(ord, c + 1) - offset != sz) |
|||
{ |
|||
throw new InvalidOperationException("Invalid coefficient order"); |
|||
} |
|||
|
|||
float invSqrtSz = 1.0f / MathF.Sqrt(sz); |
|||
|
|||
for (int i = 0; i < sz; ++i) |
|||
{ |
|||
int pos = naturalOrderBuffer[i]; |
|||
|
|||
posAndVal[i].Pos = (uint)pos; |
|||
ulong count = (ulong)((numZeros[offset + pos] * invSqrtSz) + 0.1f); |
|||
|
|||
if (count >= 1uL << 48) |
|||
{ |
|||
throw new InvalidOperationException("Count is too large"); |
|||
} |
|||
|
|||
posAndVal[i].CountAndIdx = (count << 16) | (uint)i; |
|||
} |
|||
|
|||
Array.Sort(posAndVal, 0, sz, PosAndCountComparer.Instance); |
|||
|
|||
for (int i = 0; i < sz; ++i) |
|||
{ |
|||
order[offset + i] = (int)posAndVal[i].Pos; |
|||
|
|||
isNonDefault |= naturalOrderBuffer[i] != posAndVal[i].Pos; |
|||
} |
|||
} |
|||
|
|||
if (!isNonDefault) |
|||
{ |
|||
currentUsedOrders &= ~(1u << ord); |
|||
} |
|||
} |
|||
|
|||
allUsedOrders |= currentUsedOrders; |
|||
return true; |
|||
} |
|||
|
|||
private static bool TokenizePermutation(ReadOnlySpan<uint> order, int skip, int size, List<JxlToken> tokens) |
|||
{ |
|||
uint[] lehmer = new uint[size]; |
|||
uint[] temp = new uint[size + 1]; |
|||
|
|||
if (!JxlLehmerCode.ComputeLehmerCode(order, temp, size, lehmer)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
int end = size; |
|||
while (end > skip && lehmer[end - 1] == 0) |
|||
{ |
|||
--end; |
|||
} |
|||
|
|||
tokens.Add(new JxlToken((JxlMaTreeContext)JxlCoefficientOrder.CoeffOrderContext((uint)size), (uint)(end - skip))); |
|||
|
|||
uint last = 0; |
|||
for (int i = skip; i < end; ++i) |
|||
{ |
|||
tokens.Add(new JxlToken((JxlMaTreeContext)JxlCoefficientOrder.CoeffOrderContext(last), lehmer[i])); |
|||
last = lehmer[i]; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private static bool EncodePermutation(ReadOnlySpan<uint> order, int skip, int size, JxlBitWriter writer) |
|||
{ |
|||
List<JxlToken>[] tokens = [[]]; |
|||
|
|||
if (!TokenizePermutation(order, skip, size, tokens[0])) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
JxlEntropyEncodingData codes = new(); |
|||
|
|||
if (!BuildAndEncodeHistograms(configuration, new JxlHistogramParameters(), PermutationContext, tokens, out codes, writer, out _)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return WriteTokens(tokens[0], codes, 0, writer); |
|||
} |
|||
|
|||
private static bool EncodeCoeffOrder(ReadOnlySpan<uint> order, JxlAcStrategy acs, List<JxlToken> tokens, Span<uint> orderZigzag, Span<int> naturalOrderLut) |
|||
{ |
|||
int llf = acs.CoveredBlocksX * acs.CoveredBlocksY; |
|||
int size = JxlFrameDimensions.DctBlockSize * llf; |
|||
|
|||
for (int i = 0; i < size; ++i) |
|||
{ |
|||
orderZigzag[i] = (uint)naturalOrderLut[(int)order[i]]; |
|||
} |
|||
|
|||
return TokenizePermutation(orderZigzag[..size], llf, size, tokens); |
|||
} |
|||
|
|||
public static bool EncodeCoeffOrders(ushort usedOrders, ReadOnlySpan<uint> order, JxlBitWriter writer, JxlLayerType layer, JxlAuxiliaryOutput auxOut) |
|||
{ |
|||
int memSize = JxlAcStrategy.MaximumCoefficientArea; |
|||
|
|||
// TODO: pool this? or allocate with Configuration.MemoryAllocator? memSize=65536
|
|||
uint[] orderZigzag = new uint[memSize]; |
|||
|
|||
ushort computed = 0; |
|||
List<JxlToken>[] tokens = [[]]; |
|||
int[] naturalOrderLut = []; |
|||
|
|||
for (byte o = 0; o < JxlAcStrategy.NumberOfValidStrategies; ++o) |
|||
{ |
|||
byte ord = JxlCoefficientOrder.StrategyOrder[o]; |
|||
|
|||
if ((computed & (1 << ord)) != 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
computed |= (ushort)(1 << ord); |
|||
|
|||
if ((usedOrders & (1 << ord)) == 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
JxlAcStrategy acs = new(o); |
|||
|
|||
int llf = acs.CoveredBlocksX * acs.CoveredBlocksY; |
|||
int size = JxlFrameDimensions.DctBlockSize * llf; |
|||
|
|||
if (naturalOrderLut.Length < size) |
|||
{ |
|||
naturalOrderLut = new int[size]; |
|||
} |
|||
|
|||
acs.ComputeNaturalCoefficientOrderLookup(naturalOrderLut); |
|||
|
|||
for (int c = 0; c < 3; ++c) |
|||
{ |
|||
int offset = JxlCoefficientOrder.CoeffOrderOffset(ord, c); |
|||
|
|||
if (!EncodeCoeffOrder(order.Slice(offset, size), acs, tokens[0], orderZigzag.AsSpan(0, size), naturalOrderLut)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (usedOrders != 0) |
|||
{ |
|||
JxlEntropyEncodingData codes = new(); |
|||
|
|||
if (!BuildAndEncodeHistograms(configuration, new JxlHistogramParameters(), PermutationContexts, tokens, out codes, writer, layer, auxOut, out _)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!WriteTokens(tokens[0], codes, 0, writer, layer, auxOut)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private sealed class PosAndCountComparer : IComparer<PosAndCount> |
|||
{ |
|||
public static readonly PosAndCountComparer Instance = new(); |
|||
|
|||
public int Compare(PosAndCount a, PosAndCount b) => a.CountAndIdx.CompareTo(b.CountAndIdx); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue