mirror of https://github.com/SixLabors/ImageSharp
3 changed files with 209 additions and 0 deletions
@ -0,0 +1,33 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.ProgressiveSplit; |
|||
|
|||
internal struct JxlPassDefinition |
|||
{ |
|||
public JxlPassDefinition(int numCoefficients, int shift, int suitableForDownsamplingOfAtLeast) |
|||
{ |
|||
this.NumCoefficients = numCoefficients; |
|||
this.Shift = shift; |
|||
this.SuitableForDownsamplingOfAtLeast = suitableForDownsamplingOfAtLeast; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the side of the square of the coefficients that should be kept in
|
|||
/// each 8x8 block. Must be > 1, and at most 8. Should be in non-decreasing
|
|||
/// order.
|
|||
/// </summary>
|
|||
public int NumCoefficients { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets how much to shift the encoded values by, with rounding.
|
|||
/// </summary>
|
|||
public int Shift { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value where, if specified indicates that if the required downsampling factor
|
|||
/// is sufficiently high, then it is fine to stop decoding after this pass.
|
|||
/// By default, passes are not marked as being suitable for any downsampling.
|
|||
/// </summary>
|
|||
public int SuitableForDownsamplingOfAtLeast { get; set; } |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.ProgressiveSplit; |
|||
|
|||
internal struct JxlProgressiveMode |
|||
{ |
|||
public int NumPasses = 1; |
|||
public InlineArray11<JxlPassDefinition> Passes; |
|||
|
|||
public JxlProgressiveMode() |
|||
{ |
|||
Span<JxlPassDefinition> passesSpan = this.Passes; |
|||
JxlPassDefinition definitionToFill = new(numCoefficients: 8, shift: 0, suitableForDownsamplingOfAtLeast: 1); |
|||
passesSpan.Fill(definitionToFill); |
|||
} |
|||
|
|||
public JxlProgressiveMode(Span<JxlPassDefinition> passes) |
|||
{ |
|||
int nump = passes.Length; |
|||
DebugGuard.MustBeLessThanOrEqualTo(nump, JxlShared.MaximumNumberOfPasses, nameof(nump)); |
|||
|
|||
this.NumPasses = nump; |
|||
JxlPassDefinition previousPass = new(1, 0, int.MaxValue); |
|||
int lastDownsamplingFactor = int.MaxValue; |
|||
|
|||
for (int i = 0; i < nump; i++) |
|||
{ |
|||
ref JxlPassDefinition p = ref passes[i]; |
|||
|
|||
if (!(p.NumCoefficients > previousPass.NumCoefficients || |
|||
(p.NumCoefficients == previousPass.NumCoefficients && |
|||
p.Shift < previousPass.Shift))) |
|||
{ |
|||
throw new InvalidOperationException("The pass is invalid"); |
|||
} |
|||
|
|||
if (!(p.SuitableForDownsamplingOfAtLeast == int.MaxValue || |
|||
p.SuitableForDownsamplingOfAtLeast <= lastDownsamplingFactor)) |
|||
{ |
|||
throw new InvalidOperationException("The pass is invalid"); |
|||
} |
|||
|
|||
if (p.SuitableForDownsamplingOfAtLeast != int.MaxValue) |
|||
{ |
|||
lastDownsamplingFactor = p.SuitableForDownsamplingOfAtLeast; |
|||
} |
|||
|
|||
previousPass = passes[i] = p; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,122 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.AcStrategy; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.ProgressiveSplit; |
|||
|
|||
internal struct JxlProgressiveSplitter |
|||
{ |
|||
private JxlProgressiveMode progressiveMode; |
|||
|
|||
public void SetProgressiveMode(JxlProgressiveMode mode) => this.progressiveMode = mode; |
|||
|
|||
public readonly int GetNumPasses() => this.progressiveMode.NumPasses; |
|||
|
|||
public readonly void InitializePasses(JxlPasses passes) |
|||
{ |
|||
passes.NumberOfPasses = (uint)this.GetNumPasses(); |
|||
passes.NumberOfDownsamples = 0u; |
|||
|
|||
if (passes.NumberOfPasses == 0) |
|||
{ |
|||
throw new InvalidOperationException("At least one pass must be present"); |
|||
} |
|||
|
|||
passes.Shift[(int)passes.NumberOfPasses - 1] = 0; |
|||
|
|||
if (passes.NumberOfPasses == 1) |
|||
{ |
|||
// Done. Arrays are empty.
|
|||
return; |
|||
} |
|||
|
|||
for (int i = 0; i < this.progressiveMode.NumPasses - 1; ++i) |
|||
{ |
|||
int minDownsamplingFactor = this.progressiveMode.Passes[i].SuitableForDownsamplingOfAtLeast; |
|||
passes.Shift[i] = (uint)this.progressiveMode.Passes[i].Shift; |
|||
|
|||
if (minDownsamplingFactor is > 1 and not int.MaxValue) |
|||
{ |
|||
passes.Downsample[(int)passes.NumberOfDownsamples] = (uint)minDownsamplingFactor; |
|||
passes.LastPass[(int)passes.NumberOfDownsamples] = (uint)i; |
|||
|
|||
if (this.progressiveMode.Passes[i + 1].SuitableForDownsamplingOfAtLeast < minDownsamplingFactor) |
|||
{ |
|||
passes.NumberOfDownsamples++; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public readonly void SplitAcCoefficients<T>(Span<T> block, JxlAcStrategy acs, int bx, int by, Span<InlineArray11<T>> output) |
|||
where T : unmanaged, INumber<T>, IShiftOperators<T, T, T> |
|||
{ |
|||
int size = acs.CoveredBlocksX * acs.CoveredBlocksY * JxlFrameDimensions.DctBlockSize; |
|||
|
|||
if (this.progressiveMode.NumPasses == 1) |
|||
{ |
|||
block.CopyTo(output[0]); |
|||
return; |
|||
} |
|||
|
|||
int nCoeffsAllDoneFromEarlierPasses = 1; |
|||
int previousPassShift = 0; |
|||
|
|||
for (int passNumber = 0; passNumber < this.progressiveMode.NumPasses; passNumber++) |
|||
{ |
|||
MemoryMarshal.Cast<InlineArray11<T>, T>(output[passNumber..]).Slice(0, size).Clear(); |
|||
|
|||
int passShift = this.progressiveMode.Passes[passNumber].Shift; |
|||
int frameNCoeffs = this.progressiveMode.Passes[passNumber].NumCoefficients; |
|||
int xsize = acs.CoveredBlocksX; |
|||
int ysize = acs.CoveredBlocksY; |
|||
|
|||
JxlForwardCoefficientOrder.CoefficientLayout(ref ysize, ref xsize); |
|||
|
|||
for (int y = 0; y < ysize * frameNCoeffs; y++) |
|||
{ |
|||
for (int x = 0; x < xsize * frameNCoeffs; x++) |
|||
{ |
|||
int pos = (y * xsize * JxlFrameDimensions.BlockDimensions) + x; |
|||
|
|||
if (x < xsize * nCoeffsAllDoneFromEarlierPasses && y < ysize * nCoeffsAllDoneFromEarlierPasses) |
|||
{ |
|||
// This coefficient was already included in an earlier pass,
|
|||
// which included a genuinely smaller set of coefficients.
|
|||
continue; |
|||
} |
|||
|
|||
T v = block[pos]; |
|||
|
|||
if (previousPassShift != 0) |
|||
{ |
|||
T previousV = ShiftRightRound0(v, previousPassShift) * T.CreateSaturating(1 << previousPassShift); |
|||
v -= previousV; |
|||
} |
|||
|
|||
output[passNumber][pos] = ShiftRightRound0(v, passShift); |
|||
} |
|||
} |
|||
|
|||
if (this.progressiveMode.Passes[passNumber].Shift == 0) |
|||
{ |
|||
nCoeffsAllDoneFromEarlierPasses = frameNCoeffs; |
|||
} |
|||
|
|||
previousPassShift = this.progressiveMode.Passes[passNumber].Shift; |
|||
} |
|||
|
|||
static T ShiftRightRound0(T v, int shift) |
|||
{ |
|||
T oneIfNegative = v >> T.CreateSaturating(31); |
|||
T add = (oneIfNegative << T.CreateSaturating(shift)) - oneIfNegative; |
|||
return (v + add) >> T.CreateSaturating(shift); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue