Browse Source

Quantizer improvements, reduce errors

- Use Stream for Box Content Decoder
- Reduce errors in JxlDecoderCore
- Add GetStride method to JxlFrameDecoder
- Add JxlDctQuantWeightParameters and JxlQuantizerEncoding to implement more quant_weights.h components, and add proper documentation to each member of JxlQuantMode.
- Remove InlineArray2<T> (there's already one built into System.Runtime.CompilerServices, so prefer to use that)
pull/3153/head
winscripter 1 week ago
parent
commit
4118c4a191
  1. 9
      src/ImageSharp/Formats/Jxl/InlineArrays.cs
  2. 2
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlBoxContentDecoder.cs
  3. 11
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs
  4. 25
      src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlFrameDecoder.cs
  5. 15
      src/ImageSharp/Formats/Jxl/Processing/JxlDctQuantWeightParameters.cs
  6. 34
      src/ImageSharp/Formats/Jxl/Processing/JxlQuantMode.cs
  7. 180
      src/ImageSharp/Formats/Jxl/Processing/JxlQuantizerEncoding.cs

9
src/ImageSharp/Formats/Jxl/InlineArrays.cs

@ -7,15 +7,6 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jxl; namespace SixLabors.ImageSharp.Formats.Jxl;
/// <summary>
/// Used by Butteraugli
/// </summary>
[InlineArray(2)]
internal struct InlineArray2<T>
{
private T first;
}
[InlineArray(3)] [InlineArray(3)]
internal struct InlineArray3<T> internal struct InlineArray3<T>
{ {

2
src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlBoxContentDecoder.cs

@ -56,7 +56,7 @@ internal sealed class JxlBoxContentDecoder
isUnbounded, isUnbounded,
size); size);
public void Process(Stream stream, JxlMemoryWriter writer) public void Process(Stream stream, Stream writer)
{ {
byte[] cache = ArrayPool<byte>.Shared.Rent(16384); byte[] cache = ArrayPool<byte>.Shared.Rent(16384);

11
src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs

@ -2138,11 +2138,8 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
Span<byte> startSlice = bufferSpan[(int)this.boxOutBufferPos..]; Span<byte> startSlice = bufferSpan[(int)this.boxOutBufferPos..];
int status = this.boxContentDecoder!.Process( int status = this.boxContentDecoder!.Process(
this.nextInput, stream,
this.availableInput, this.boxOutputBuffer);
this.filePosition - this.boxContentsBegin,
nextOut,
ref availOut);
long produced = startSlice.Length - availOut; long produced = startSlice.Length - availOut;
this.boxOutBufferPos += produced; this.boxOutBufferPos += produced;
@ -2290,7 +2287,7 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
ThrowNotEnoughData(); ThrowNotEnoughData();
} }
if (this.inputClosed || (this.eventsWanted & JxlDecoderStatus.) != 0) if (this.inputClosed || (this.eventsWanted & JxlDecoderStatus.Box) != 0)
{ {
return JxlDecoderStatus.Success; return JxlDecoderStatus.Success;
} }
@ -2704,7 +2701,7 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
int status = this.ProcessBoxes(stream); int status = this.ProcessBoxes(stream);
if (status == Success) if (status == JxlDecoderStatus.Success)
{ {
if (this.CanUseMoreCodestreamInput()) if (this.CanUseMoreCodestreamInput())
{ {

25
src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlFrameDecoder.cs

@ -32,6 +32,31 @@ internal sealed class JxlFrameDecoder
private JxlProgressiveDetail progressiveDetail = JxlProgressiveDetail.Frames; private JxlProgressiveDetail progressiveDetail = JxlProgressiveDetail.Frames;
private List<int> passesToPause = []; private List<int> passesToPause = [];
/// <summary>
/// Gets a value indicating whether there are any DC groups left to decode.
/// </summary>
private bool ContainsDcGroupToDecode => this.decodedDcGroups.Any(x => x == 0);
private static int GetStride(int width, JxlPixelFormat format)
{
if (!JxlMath.SafeMultiply(BytesPerChannel(format.DataType), format.Channels, out int xStride))
{
throw new InvalidOperationException("Image too large");
}
if (!JxlMath.SafeMultiply(xStride, width, out int yStride))
{
throw new InvalidOperationException("Image too large");
}
if (!JxlMath.SafeRoundUpTo(yStride, format.Align, yStride))
{
throw new InvalidOperationException("Image too large");
}
return yStride;
}
public static void DecodeGlobalDcInfo(Configuration configuration, JxlBitReader reader, bool isJpeg, JxlPassesDecoderState state) public static void DecodeGlobalDcInfo(Configuration configuration, JxlBitReader reader, bool isJpeg, JxlPassesDecoderState state)
{ {
state.SharedStorage.Quantizer.Decode(reader); state.SharedStorage.Quantizer.Decode(reader);

15
src/ImageSharp/Formats/Jxl/Processing/JxlDctQuantWeightParameters.cs

@ -8,21 +8,22 @@ internal sealed class JxlDctQuantWeightParameters
private const int Log2MaxDistanceBands = 4; private const int Log2MaxDistanceBands = 4;
private const int MaxDistanceBands = 1 + (1 << Log2MaxDistanceBands); private const int MaxDistanceBands = 1 + (1 << Log2MaxDistanceBands);
private int numDistanceBands;
private readonly float[][] distanceBands;
public JxlDctQuantWeightParameters() public JxlDctQuantWeightParameters()
{ {
this.distanceBands = new float[3][]; this.DistanceBands = new float[3][];
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
{ {
this.distanceBands[i] = new float[MaxDistanceBands]; this.DistanceBands[i] = new float[MaxDistanceBands];
} }
} }
public JxlDctQuantWeightParameters(float[][] distanceBands, int numDistanceBands) public JxlDctQuantWeightParameters(float[][] distanceBands, int numDistanceBands)
{ {
this.numDistanceBands = numDistanceBands; this.NumDistanceBands = numDistanceBands;
this.distanceBands = distanceBands; this.DistanceBands = distanceBands;
} }
public int NumDistanceBands { get; set; }
public float[][] DistanceBands { get; }
} }

34
src/ImageSharp/Formats/Jxl/Processing/JxlQuantMode.cs

@ -4,16 +4,48 @@
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
/// <summary> /// <summary>
/// Quantization mode. /// Specifies which algorithm should be used to quantize coefficients.
/// </summary> /// </summary>
internal enum JxlQuantMode : byte internal enum JxlQuantMode : byte
{ {
/// <summary>
/// The quantizer relies on predefined tables for quantization.
/// The idea is similar to Huffman coding.
/// </summary>
Library, Library,
/// <summary>
/// The quantizer uses an Identity transform.
/// </summary>
Id, Id,
/// <summary>
/// The quantizer uses a 2x2 Discrete Cosine Transform.
/// </summary>
Dct2, Dct2,
/// <summary>
/// The quantizer uses a 4x4 Discrete Cosine Transform.
/// </summary>
Dct4, Dct4,
/// <summary>
/// The quantizer uses a 4x8 Discrete Cosine Transform.
/// </summary>
Dct4x8, Dct4x8,
/// <summary>
/// The quantizer uses the AFV transform.
/// </summary>
Afv, Afv,
/// <summary>
/// The quantizer uses a Discrete Cosine Transform with custom block size.
/// </summary>
Dct, Dct,
/// <summary>
/// No quantization is performed. Input data becomes the output as-is.
/// </summary>
Raw Raw
} }

180
src/ImageSharp/Formats/Jxl/Processing/JxlQuantizerEncoding.cs

@ -0,0 +1,180 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
/// <summary>
/// Specifies weights and quantizer modes.
/// </summary>
internal sealed class JxlQuantizerEncoding
{
/// <summary>
/// Gets or sets the kind of transform used for this quantizer encoding.
/// </summary>
public JxlQuantMode Mode { get; set; }
/// <summary>
/// Gets or sets the weights for DCT4+ tables.
/// </summary>
public JxlDctQuantWeightParameters? DctParameters { get; set; }
/// <summary>
/// Gets or sets the weights for the 4x4 sub-block in AFV.
/// </summary>
public JxlDctQuantWeightParameters? DctParametersAfv4x4 { get; set; }
/// <summary>
/// Gets or sets the weights for the identity transform.
/// </summary>
public InlineArray3<InlineArray3<float>> IdWeights { get; set; }
/// <summary>
/// Gets or sets the weights for the DCT2 transform.
/// </summary>
public InlineArray3<InlineArray6<float>> Dct2Weights { get; set; }
/// <summary>
/// Gets or sets the multipliers for the DCT4 transform.
/// </summary>
public InlineArray3<InlineArray6<float>> Dct4Multipliers { get; set; }
/// <summary>
/// Gets or sets the weights for the AFV transform.
/// </summary>
public InlineArray3<InlineArray9<float>> AfvWeights { get; set; }
/// <summary>
/// Gets or sets the multipliers for the 4x8 DCT block-based transform.
/// </summary>
public InlineArray3<float> Dct4x8Multipliers { get; set; }
/// <summary>
/// Gets or sets the explicit quantization table (like in JPEG).
/// </summary>
/// <remarks>
/// Only used when <see cref="Mode"/> == <see cref="JxlQuantMode.Raw"/>.
/// </remarks>
public int[]? QuantizationTable { get; set; }
/// <summary>
/// Gets or sets the denominator for each item in the explicit quantization table.
/// </summary>
/// <remarks>
/// Only used when <see cref="Mode"/> == <see cref="JxlQuantMode.Raw"/>.
/// </remarks>
public float QuantizationTableDenominator { get; set; } = 1f / (8 * 255);
/// <summary>
/// Gets or sets a value indicating which predefined table to use. The value is
/// only used when <see cref="Mode"/> == <see cref="JxlQuantMode.Library"/>.
/// </summary>
public byte Predefined { get; set; }
/// <summary>
/// Creates a new quantizer encoding with the Library quantizer mode
/// and the specified library index.
/// </summary>
/// <param name="libraryIndex">The library index (aka predefined table).</param>
/// <returns>A new Library quantizer encoding.</returns>
public static JxlQuantizerEncoding Library(int libraryIndex)
{
DebugGuard.MustBeLessThan(libraryIndex, JxlQuantWeights.NumPredefinedTables, nameof(libraryIndex));
return new()
{
Mode = JxlQuantMode.Library,
Predefined = (byte)libraryIndex
};
}
/// <summary>
/// Creates a new quantizer encoding with the Identity quantizer mode
/// and the specified XYB/identity weights.
/// </summary>
/// <param name="xybWeights">Weights for the identity transform.</param>
/// <returns>A new Identity quantizer encoding.</returns>
public static JxlQuantizerEncoding Identity(in InlineArray3<InlineArray3<float>> xybWeights)
=> new()
{
Mode = JxlQuantMode.Id,
IdWeights = xybWeights
};
/// <summary>
/// Creates a new quantizer encoding with the DCT2x2 quantizer mode
/// and the specified XYB/DCT2x2 weights.
/// </summary>
/// <param name="xybWeights">Weights for the DCT2x2 transform.</param>
/// <returns>A new DCT2x2 quantizer encoding.</returns>
public static JxlQuantizerEncoding Dct2(in InlineArray3<InlineArray6<float>> xybWeights)
=> new()
{
Mode = JxlQuantMode.Dct2,
Dct2Weights = xybWeights
};
/// <summary>
/// Creates a new quantizer encoding with the DCT4x4 quantizer mode,
/// the specified XYB/DCT4x4 multipliers, and quantizer weight parameters.
/// </summary>
/// <param name="parameters">Quantizer weights for the DCT4x4 transform.</param>
/// <param name="xybMul">XYB multipliers for the DCT4x4 transform.</param>
/// <returns>A new DCT4x4 quantizer encoding.</returns>
public static JxlQuantizerEncoding Dct4(JxlDctQuantWeightParameters parameters, in InlineArray3<InlineArray6<float>> xybMul)
=> new()
{
Mode = JxlQuantMode.Dct4,
DctParameters = parameters,
Dct4Multipliers = xybMul
};
/// <summary>
/// Creates a new quantizer encoding with the DCT4x8 quantizer mode,
/// the specified XYB/DCT4x8 multipliers, and quantizer weight parameters.
/// </summary>
/// <param name="parameters">Quantizer weights for the DCT4x8 transform.</param>
/// <param name="xybMul">XYB multipliers for the DCT4x8 transform.</param>
/// <returns>A new DCT4x8 quantizer encoding.</returns>
public static JxlQuantizerEncoding Dct4x8(JxlDctQuantWeightParameters parameters, in InlineArray3<float> xybMul)
=> new()
{
Mode = JxlQuantMode.Dct4x8,
DctParameters = parameters,
Dct4x8Multipliers = xybMul
};
/// <summary>
/// Creates a new quantizer encoding with the DCT quantizer mode
/// and quantizer weight parameters.
/// </summary>
/// <param name="parameters">Quantizer weights for the DCT transform.</param>
/// <returns>A new DCT quantizer encoding.</returns>
public static JxlQuantizerEncoding Dct(JxlDctQuantWeightParameters parameters)
=> new()
{
Mode = JxlQuantMode.Dct,
DctParameters = parameters,
};
/// <summary>
/// Creates a new quantizer encoding with the AFV quantizer mode,
/// quantizer weight parameters for 4x8/4x4 blocks, and weights.
/// </summary>
/// <param name="params4x8">Quantizer weights for the 4x8 sub-block for the AFV transform.</param>
/// <param name="params4x4">Quantizer weights for the 4x4 sub-block for the AFV transform.</param>
/// <param name="weights">Quantizer weights.</param>
/// <returns>A new DCT quantizer encoding.</returns>
public static JxlQuantizerEncoding Afv(
JxlDctQuantWeightParameters params4x8,
JxlDctQuantWeightParameters params4x4,
in InlineArray3<InlineArray9<float>> weights)
=> new()
{
Mode = JxlQuantMode.Afv,
DctParameters = params4x8,
AfvWeights = weights,
DctParametersAfv4x4 = params4x4
};
}
Loading…
Cancel
Save