Browse Source

Fully implement JPEG XL fields

See fields.h and fields.cc
pull/3153/head
winscripter 2 months ago
parent
commit
decc0b232c
  1. 35
      src/ImageSharp/Formats/Jxl/Fields/JxlAllDefaultVisitor.cs
  2. 41
      src/ImageSharp/Formats/Jxl/Fields/JxlBitsCoder.cs
  3. 89
      src/ImageSharp/Formats/Jxl/Fields/JxlBundle.cs
  4. 118
      src/ImageSharp/Formats/Jxl/Fields/JxlCanEncodeVisitor.cs
  5. 42
      src/ImageSharp/Formats/Jxl/Fields/JxlExtensionStates.cs
  6. 73
      src/ImageSharp/Formats/Jxl/Fields/JxlF16Coder.cs
  7. 51
      src/ImageSharp/Formats/Jxl/Fields/JxlInitVisitor.cs
  8. 132
      src/ImageSharp/Formats/Jxl/Fields/JxlReadVisitor.cs
  9. 49
      src/ImageSharp/Formats/Jxl/Fields/JxlSetDefaultVisitor.cs
  10. 127
      src/ImageSharp/Formats/Jxl/Fields/JxlU32Coder.cs
  11. 105
      src/ImageSharp/Formats/Jxl/Fields/JxlU64Coder.cs
  12. 92
      src/ImageSharp/Formats/Jxl/Fields/JxlVisitor.cs
  13. 74
      src/ImageSharp/Formats/Jxl/Fields/JxlVisitorBase.cs
  14. 23
      src/ImageSharp/Formats/Jxl/IO/JxlBitReader.cs
  15. 2
      src/ImageSharp/Formats/Jxl/Metadata/JxlBitDepth.cs
  16. 2
      src/ImageSharp/Formats/Jxl/Metadata/JxlCustomTransformData.cs

35
src/ImageSharp/Formats/Jxl/Fields/JxlAllDefaultVisitor.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
internal sealed class JxlAllDefaultVisitor : JxlVisitorBase
{
public bool IsAllDefault { get; private set; } = true;
public override bool Bits(int bits, uint defaultValue, ref uint value)
{
this.IsAllDefault = value == defaultValue;
return true;
}
public override bool U32(JxlU32Enc enc, uint defaultValue, ref uint value)
{
this.IsAllDefault = value == defaultValue;
return true;
}
public override bool U64(ulong defaultValue, ref ulong value)
{
this.IsAllDefault = value == defaultValue;
return true;
}
public override bool F16(float defaultValue, ref float value)
{
this.IsAllDefault = MathF.Abs(value - defaultValue) < 1E-6f;
return true;
}
public override bool AllDefault(IJxlFields fields, ref bool allDefault) => false;
}

41
src/ImageSharp/Formats/Jxl/Fields/JxlBitsCoder.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jxl.IO;
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
/// <summary>
/// Raw bits coder
/// </summary>
internal static class JxlBitsCoder
{
/// <summary>
/// Maximum number of encodeable bits. Since this coder encodes
/// bits raw, this happens to be whatever is passed to it.
/// </summary>
// Looks like that's what the function does (fields.cc:406):
// it returns whatever is passed to it.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int MaxEncodedBits(int bits) => bits;
public static bool CanEncode(int bits, uint value, ref int encodedBits)
{
encodedBits = bits;
if (value >= (1 << bits))
{
DebugGuard.IsTrue(false, "Value is too large");
return false;
}
return true;
}
// NOTE: BitsCoder::Read (fields.cc:418) returns a uint32_t,
// suggesting the input bit size does not exceed 32 bits.
public static uint Read(uint bits, JxlBitReader reader) => reader.ReadBits32(bits);
public static uint Read(int bits, JxlBitReader reader) => reader.ReadBits32((uint)bits);
}

89
src/ImageSharp/Formats/Jxl/Fields/JxlBundle.cs

@ -0,0 +1,89 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jxl.IO;
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
/// <summary>
/// An <see cref="IJxlFields"/> helper.
/// </summary>
internal static class JxlBundle
{
/// <summary>
/// Initializes the specified JXL fields.
/// </summary>
/// <param name="fields">The JXL fields.</param>
public static void Init(IJxlFields fields)
{
JxlInitVisitor initVisitor = new();
if (!initVisitor.Visit(fields))
{
DebugGuard.IsTrue(false, "Init should never fail");
}
}
/// <summary>
/// Sets all JXL fields provided by the input value to their defaults.
/// </summary>
/// <param name="fields">The JXL fields.</param>
public static void SetDefault(IJxlFields fields)
{
JxlSetDefaultVisitor visitor = new();
if (!visitor.Visit(fields))
{
DebugGuard.IsTrue(false, "SetDefault should never fail");
}
}
/// <summary>
/// Returns a value indicating whether every value provided by this
/// field is a default value. If at least one field isn't a default
/// value, the method returns false.
/// </summary>
/// <param name="fields">The JXL fields.</param>
/// <returns>A boolean indicating whether or not are all values initialized to their default values.</returns>
public static bool AllDefault(IJxlFields fields)
{
JxlAllDefaultVisitor allDefaultVisitor = new();
if (!allDefaultVisitor.Visit(fields))
{
DebugGuard.IsTrue(false, "AllDefault should never fail");
}
return allDefaultVisitor.IsAllDefault;
}
/// <summary>
/// Reads the fields from a bit-reader.
/// </summary>
/// <param name="reader">The bit-reader.</param>
/// <param name="fields">The fields.</param>
/// <returns>Status of the read operation.</returns>
public static bool Read(JxlBitReader reader, IJxlFields fields)
{
JxlReadVisitor visitor = new(reader);
if (!visitor.Visit(fields))
{
return false;
}
return visitor.OK;
}
/// <summary>
/// Tries to read the fields from a bit-reader.
/// </summary>
/// <param name="reader">The bit-reader.</param>
/// <param name="fields">The fields.</param>
/// <returns>Status of the read operation.</returns>
public static bool CanRead(JxlBitReader reader, IJxlFields fields)
{
JxlReadVisitor visitor = new(reader);
_ = visitor.Visit(fields);
return visitor.OK;
}
}

118
src/ImageSharp/Formats/Jxl/Fields/JxlCanEncodeVisitor.cs

@ -0,0 +1,118 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
internal sealed class JxlCanEncodeVisitor : JxlVisitorBase
{
private long encodedBits;
private ulong extensions;
private long posAfterExt;
public bool OK { get; set; } = true;
public override bool Bits(int bits, uint defaultValue, ref uint value)
{
int enc = 0;
this.OK &= JxlBitsCoder.CanEncode(bits, value, ref enc);
this.encodedBits += enc;
return true;
}
public override bool U32(JxlU32Enc enc, uint defaultValue, ref uint value)
{
int encBits = 0;
this.OK &= JxlU32Coder.CanEncode(enc, value, ref encBits);
this.encodedBits += encBits;
return true;
}
public override bool U64(ulong defaultValue, ref ulong value)
{
int encBits = 0;
this.OK &= JxlU64Coder.CanEncode(value, ref encBits);
this.encodedBits += encBits;
return true;
}
public override bool F16(float defaultValue, ref float value)
{
int encBits = 0;
this.OK &= JxlF16Coder.CanEncode(value, ref encBits);
this.encodedBits += encBits;
return true;
}
public override bool AllDefault(IJxlFields fields, ref bool allDefault)
{
allDefault = JxlBundle.AllDefault(fields);
if (!this.Boolean(true, ref allDefault))
{
return false;
}
return allDefault;
}
public override bool BeginExtensions(ref ulong extensions)
{
if (!base.BeginExtensions(ref extensions))
{
return false;
}
this.extensions = extensions;
if (extensions != 0uL)
{
if (this.posAfterExt != 0)
{
return false;
}
this.posAfterExt = this.encodedBits;
if (this.posAfterExt == 0)
{
return false;
}
}
return true;
}
public bool GetSizes(ref int extensionBits, ref long totalBits)
{
if (!this.OK)
{
return false;
}
extensionBits = 0;
totalBits = this.encodedBits;
if (this.posAfterExt != 0)
{
if (this.encodedBits < this.posAfterExt)
{
return false;
}
extensionBits = (int)this.encodedBits - (int)this.posAfterExt;
int encodedBits = 0;
this.OK &= JxlU64Coder.CanEncode(extensionBits, ref encodedBits);
totalBits += encodedBits;
for (int i = 1; i < BitOperations.PopCount(this.extensions); i++)
{
encodedBits = 0;
this.OK &= JxlU64Coder.CanEncode(0, ref encodedBits);
totalBits += encodedBits;
}
}
return true;
}
}

42
src/ImageSharp/Formats/Jxl/Fields/JxlExtensionStates.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
internal sealed class JxlExtensionStates
{
private ulong begun;
private ulong ended;
public bool IsBegun => (this.begun & 1) != 0;
public bool IsEnded => (this.ended & 1) != 0;
public void Push()
{
this.begun <<= 1;
this.ended <<= 1;
}
public void Pop()
{
this.begun >>= 1;
this.ended >>= 1;
}
public void Begin()
{
DebugGuard.IsFalse(this.IsBegun, nameof(this.IsBegun), "This must be false.");
DebugGuard.IsFalse(this.IsEnded, nameof(this.IsEnded), "This must be false.");
this.begun++;
}
public void End()
{
DebugGuard.IsTrue(this.IsBegun, nameof(this.IsBegun), "This must be true.");
DebugGuard.IsFalse(this.IsEnded, nameof(this.IsEnded), "This must be false.");
this.ended++;
}
}

73
src/ImageSharp/Formats/Jxl/Fields/JxlF16Coder.cs

@ -0,0 +1,73 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jxl.IO;
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
/// <summary>
/// Represents the Half-precision Floating-point number coder.
/// </summary>
internal static class JxlF16Coder
{
/// <summary>
/// Always returns 16, which is the maximum possible encoded bits.
/// The F16 coder always reads 16 bits from the bitstream.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int MaxEncodedBits() => 16;
/// <summary>
/// Returns a boolean indicating whether the input float
/// can be represented properly when encoded into a bit-stream.
/// Also stores the maximum encodeable bits into encodedBits (which is
/// always 16).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool CanEncode(float value, ref int encodedBits)
{
encodedBits = MaxEncodedBits();
if (float.IsNaN(value) || float.IsInfinity(value))
{
return false; // NaN and Infinity are not valid
}
return MathF.Abs(value) <= 65504.0f;
}
public static bool Read(JxlBitReader reader, ref float value)
{
uint bits16 = reader.ReadBits32(16u);
uint sign = bits16 >> 15;
uint biasedExponent = (bits16 >> 10) & 0x1Fu;
uint mantissa = bits16 & 0x3FFu;
if (biasedExponent == 31u)
{
// NaN and Infinity are not valid
return false;
}
if (biasedExponent == 0u)
{
// Subnormal or zero.
value = (1.0f / 16384) * (mantissa * (1.0f / 1024));
if (sign != 0u)
{
value = -value;
}
return true;
}
uint biasedExp32 = biasedExponent + (127u - 15u);
uint mantissa32 = mantissa << (23 - 10);
uint bits32 = (sign << 31) | (biasedExp32 << 23) | mantissa32;
value = BitConverter.UInt32BitsToSingle(bits32);
return true;
}
}

51
src/ImageSharp/Formats/Jxl/Fields/JxlInitVisitor.cs

@ -0,0 +1,51 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
/// <summary>
/// This variant of <see cref="JxlVisitorBase"/> sets values
/// to be the default value.
/// </summary>
internal sealed class JxlInitVisitor : JxlVisitorBase
{
public override bool Bits(int bits, uint defaultValue, ref uint value)
{
value = defaultValue;
return true;
}
public override bool U32(JxlU32Distribution d0, JxlU32Distribution d1, JxlU32Distribution d2, JxlU32Distribution d3, uint defaultValue, ref uint value)
{
value = defaultValue;
return true;
}
public override bool U64(ulong defaultValue, ref ulong value)
{
value = defaultValue;
return true;
}
public override bool Boolean(bool defaultValue, ref bool value)
{
value = defaultValue;
return true;
}
public override bool F16(float defaultValue, ref float value)
{
value = defaultValue;
return true;
}
public override bool Conditional(bool condition) => true;
public override bool AllDefault(IJxlFields fields, ref bool allDefault)
{
_ = this.Boolean(true, ref allDefault);
return false;
}
public override bool VisitNested(IJxlFields fields) => true;
}

132
src/ImageSharp/Formats/Jxl/Fields/JxlReadVisitor.cs

@ -0,0 +1,132 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jxl.IO;
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
internal sealed class JxlReadVisitor(JxlBitReader reader) : JxlVisitorBase
{
private ulong totalExtensionBits;
private bool notEnoughBytes;
private long posAfterExtSize;
private readonly ulong[] extensionBits = new ulong[JxlBundle.MaxExtensions];
public bool OK { get; private set; }
public override bool IsReading => true;
public override bool Bits(int bits, uint defaultValue, ref uint value)
{
value = JxlBitsCoder.Read(bits, reader);
return this.ThrowIfEndOfStreamOrReturnTrue();
}
public override bool U32(JxlU32Enc enc, uint defaultValue, ref uint value)
{
value = JxlU32Coder.Read(enc, reader);
return this.ThrowIfEndOfStreamOrReturnTrue();
}
public override bool U64(ulong defaultValue, ref ulong value)
{
value = JxlU64Coder.Read(reader);
return this.ThrowIfEndOfStreamOrReturnTrue();
}
public override bool F16(float defaultValue, ref float value)
{
this.OK &= JxlF16Coder.Read(reader, ref value);
return this.ThrowIfEndOfStreamOrReturnTrue();
}
public override void SetDefault(IJxlFields fields) => JxlBundle.SetDefault(fields);
public override bool BeginExtensions(ref ulong extensions)
{
if (!base.BeginExtensions(ref extensions))
{
return false;
}
if (extensions == 0)
{
return true;
}
for (ulong remainingExtensions = extensions; remainingExtensions != 0; remainingExtensions &= remainingExtensions - 1)
{
int idxExtension = Num0BitsBelowLS1BitNonzero(remainingExtensions);
if (!this.U64(0, ref this.extensionBits[idxExtension]))
{
return false;
}
if (!SafeAdd(this.totalExtensionBits, this.extensionBits[idxExtension], ref this.totalExtensionBits))
{
DebugGuard.IsTrue(false, "Extension bits overflow; the codestream is not valid");
return false;
}
}
this.posAfterExtSize = reader.TotalBitsConsumed;
return this.posAfterExtSize != 0;
}
public override bool EndExtensions()
{
if (!base.EndExtensions())
{
return false;
}
if (this.posAfterExtSize == 0)
{
return true;
}
if (this.notEnoughBytes)
{
return true;
}
long bitsRead = reader.TotalBitsConsumed;
long end = 0;
if (!SafeAdd(this.posAfterExtSize, this.totalExtensionBits, ref end))
{
DebugGuard.IsTrue(false, "Invalid extension size.");
return false;
}
if (bitsRead > end)
{
DebugGuard.IsTrue(false, "Read more extension bits than budgeted");
return false;
}
long remainingBits = end - bitsRead;
if (remainingBits != 0)
{
reader.SkipBits64((uint)remainingBits);
}
return this.ThrowIfEndOfStreamOrReturnTrue();
}
private bool ThrowIfEndOfStreamOrReturnTrue()
{
if (reader.IsEndOfStream)
{
DebugGuard.IsTrue(false, "Got an invalid end-of-stream");
this.notEnoughBytes = true;
return true;
}
return true;
}
}

49
src/ImageSharp/Formats/Jxl/Fields/JxlSetDefaultVisitor.cs

@ -0,0 +1,49 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
/// <summary>
/// This is similar to InitVisitor but also initializes
/// nested fields.
/// </summary>
internal sealed class JxlSetDefaultVisitor : JxlVisitorBase
{
public override bool Bits(int bits, uint defaultValue, ref uint value)
{
value = defaultValue;
return true;
}
public override bool U32(JxlU32Distribution d0, JxlU32Distribution d1, JxlU32Distribution d2, JxlU32Distribution d3, uint defaultValue, ref uint value)
{
value = defaultValue;
return true;
}
public override bool U64(ulong defaultValue, ref ulong value)
{
value = defaultValue;
return true;
}
public override bool Boolean(bool defaultValue, ref bool value)
{
value = defaultValue;
return true;
}
public override bool F16(float defaultValue, ref float value)
{
value = defaultValue;
return true;
}
public override bool Conditional(bool condition) => true;
public override bool AllDefault(IJxlFields fields, ref bool allDefault)
{
_ = this.Boolean(true, ref allDefault);
return false;
}
}

127
src/ImageSharp/Formats/Jxl/Fields/JxlU32Coder.cs

@ -0,0 +1,127 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jxl.IO;
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
/// <summary>
/// Unsigned 32-bit variable-length integer coder.
/// </summary>
internal static class JxlU32Coder
{
/// <summary>
/// Maximum number of writeable and/or readable bits in a variable-length integer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int MaxEncodedBits(in JxlU32Enc enc)
{
int extraBits = 0;
for (int selector = 0; selector < 4; selector++)
{
JxlU32Distribution distr = enc.GetDistribution(selector);
if (distr.IsDirect)
{
continue;
}
else
{
extraBits = Math.Max(extraBits, (int)distr.ExtraBits);
}
}
return 2 + extraBits;
}
/// <summary>
/// Verifies that the value can be encoded.
/// </summary>
public static bool CanEncode(in JxlU32Enc enc, uint value, ref int encodedBits)
{
uint selector = 0;
int totalBits = 0;
bool isOk = ChooseSelector(in enc, value, ref selector, ref totalBits);
encodedBits = isOk ? totalBits : 0;
return isOk;
}
/// <summary>
/// Reads the U32 coded value.
/// </summary>
public static uint Read(in JxlU32Enc enc, JxlBitReader reader)
{
uint selector = reader.ReadBits32(2u);
JxlU32Distribution dist = enc.GetDistribution((int)selector);
if (dist.IsDirect)
{
return dist.Direct;
}
else
{
return reader.ReadBits32(dist.ExtraBits) + dist.Offset;
}
}
/// <summary>
/// Tries to find the best one of the four selectors based on the value.
/// </summary>
public static bool ChooseSelector(in JxlU32Enc enc, uint value, ref uint selector, ref int totalBits)
{
int bitsRequired = 32 - Num0BitsAboveMS1Bit(value);
if (bitsRequired > 32)
{
return false;
}
selector = 0;
totalBits = 64;
for (int s = 0; s < 4; s++)
{
JxlU32Distribution dist = enc.GetDistribution(s);
if (dist.IsDirect)
{
if (dist.Direct == value)
{
selector = (uint)s;
totalBits = 2;
return true;
}
continue;
}
uint extraBits = dist.ExtraBits;
uint offset = dist.Offset;
if (value < offset || value >= offset + (1u << (int)extraBits))
{
continue;
}
if (2 + extraBits < totalBits)
{
selector = (uint)s;
totalBits = 2 + (int)extraBits;
}
}
if (totalBits == 64)
{
DebugGuard.IsTrue(false, "No matching selector");
return false;
}
return true;
}
}

105
src/ImageSharp/Formats/Jxl/Fields/JxlU64Coder.cs

@ -0,0 +1,105 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jxl.IO;
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
/// <summary>
/// Unsigned 64-bit variable-length coding.
/// </summary>
internal static class JxlU64Coder
{
/// <summary>
/// Reads the variable-length, unsigned 64-bit integer.
/// </summary>
public static ulong Read(JxlBitReader reader)
{
uint selector = reader.ReadBits32(2u);
if (selector == 0u)
{
return 0u;
}
else if (selector == 1u)
{
return 1u + reader.ReadBits32(4u);
}
else if (selector == 2u)
{
return 17u + reader.ReadBits32(8u);
}
// Selector 3...
ulong result = reader.ReadBits32(12u);
int shift = 12;
while (reader.ReadBoolean())
{
if (shift == 60)
{
result |= (ulong)reader.ReadBits32(4u) << shift;
break;
}
result |= (ulong)reader.ReadBits32(8u) << shift;
shift += 8;
}
return result;
}
/// <summary>
/// Returns a value indicating whether can the value be encoded,
/// as well as the number of encoded bits.
/// </summary>
public static bool CanEncode(ulong value, ref int encodedBits)
{
if (value == 0)
{
// 2 selector bits
encodedBits = 2;
}
else if (value <= 16)
{
// 2 selector bits + 4 payload bits
encodedBits = 2 + 4;
}
else if (value <= 272)
{
// 2 selector bits + 8 payload bits
encodedBits = 2 + 8;
}
else
{
// 2 selector bits + 12 payload bits
encodedBits = 2 + 12;
value >>= 12;
int shift = 12;
while (value > 0 && shift < 60)
{
// 1 continuation bit + 8 payload bits
encodedBits += 1 + 8;
value >>= 8;
shift += 8;
}
if (value > 0)
{
// 1 continuation bit + 4 payload bits
encodedBits += 1 + 4;
}
else
{
// 1 stop bit
encodedBits += 1;
}
}
return true;
}
/// <summary>
/// Always returns 73.
/// </summary>
public static int MaxEncodedBits() => 73;
}

92
src/ImageSharp/Formats/Jxl/Fields/JxlVisitor.cs

@ -0,0 +1,92 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
/// <summary>
/// Base JPEG XL visitor that can visit all fields of a class.
/// This is highly similar to the following Reflection code, but with
/// lower overhead:
/// <code>
/// // Pseudocode
/// void Visit(Type type)
/// {
/// foreach (PropertyInfo property in type.GetProperties())
/// {
/// /* visitor implementation */(property);
/// }
/// }
/// </code>
/// </summary>
internal class JxlVisitor
{
public virtual bool IsReading => false;
public virtual bool Visit(IJxlFields fields) => false;
public virtual bool Boolean(bool defaultValue, ref bool value) => false;
public virtual bool U32(JxlU32Enc enc, uint defaultValue, ref uint value) => false;
public virtual bool U32(
JxlU32Distribution d0,
JxlU32Distribution d1,
JxlU32Distribution d2,
JxlU32Distribution d3,
uint defaultValue,
ref uint value)
=> this.U32(new JxlU32Enc(d0, d1, d2, d3), value, ref defaultValue);
public virtual unsafe bool Enum<T>(T defaultValue, ref T value)
where T : unmanaged
{
DebugGuard.IsTrue(sizeof(T) == 4, "We use unsafe bit casting so anything beside 4 bytes will break memory layout");
ref uint u32 = ref Unsafe.As<T, uint>(ref value);
if (!this.U32(
JxlFieldExpressions.Value(0),
JxlFieldExpressions.Value(1),
JxlFieldExpressions.BitsOffset(4, 2),
JxlFieldExpressions.BitsOffset(6, 18),
Unsafe.BitCast<T, uint>(defaultValue),
ref u32))
{
return false;
}
return System.Enum.IsDefined(typeof(T), value);
}
public virtual bool Bits(int bits, uint defaultValue, ref uint value) => false;
public virtual bool U64(ulong defaultValue, ref ulong value) => false;
public virtual bool F16(float defaultValue, ref float value) => false;
public virtual bool Conditional(bool condition) => condition;
public virtual bool AllDefault(IJxlFields fields, ref bool allDefault)
{
// Do not remove the fields parameter, derived classes
// use it.
if (!this.Boolean(true, ref allDefault))
{
return false;
}
return allDefault;
}
public virtual void SetDefault(IJxlFields fields)
{
// Used by derived methods.
}
public virtual bool VisitNested(IJxlFields fields) => this.Visit(fields);
public virtual bool BeginExtensions(ref ulong extensions) => false;
public virtual bool EndExtensions() => false;
}

74
src/ImageSharp/Formats/Jxl/Fields/JxlVisitorBase.cs

@ -0,0 +1,74 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics;
#pragma warning disable SA1405 // Debug.Assert should provide message text
namespace SixLabors.ImageSharp.Formats.Jxl.Fields;
internal class JxlVisitorBase : JxlVisitor
{
private readonly JxlExtensionStates extensionStates = new();
private int depth;
public override bool Visit(IJxlFields fields)
{
if (this.depth >= JxlBundle.MaxExtensions)
{
return false;
}
this.depth++;
this.extensionStates.Push();
bool visited = fields.Visit(this);
if (visited)
{
// TODO: use DebugGuard
Debug.Assert(!this.extensionStates.IsBegun || this.extensionStates.IsEnded);
}
this.extensionStates.Pop();
// TODO: use DebugGuard
Debug.Assert(this.depth != 0);
this.depth--;
return visited;
}
public override bool Boolean(bool defaultValue, ref bool value)
{
uint bits = value ? 1u : 0u;
if (!this.Bits(1, defaultValue ? 1u : 0u, ref bits))
{
return false;
}
// TODO: use DebugGuard
Debug.Assert(bits <= 1u);
value = bits == 1u;
return true;
}
public override bool BeginExtensions(ref ulong extensions)
{
if (!this.U64(0uL, ref extensions))
{
return false;
}
this.extensionStates.Begin();
return true;
}
public override bool EndExtensions()
{
this.extensionStates.End();
return true;
}
}

23
src/ImageSharp/Formats/Jxl/IO/JxlBitReader.cs

@ -14,7 +14,16 @@ internal sealed class JxlBitReader(ReadOnlyMemory<byte> bytes)
private ulong buffer;
private uint bufferRemainingBits;
private int pointer;
private bool endOfStream;
/// <summary>
/// Gets a value indicating whether this marks an end of stream.
/// </summary>
public bool IsEndOfStream { get; private set; }
/// <summary>
/// Gets the total number of bits consumed.
/// </summary>
public long TotalBitsConsumed => ((long)this.pointer * 8) + (64 - this.bufferRemainingBits);
/// <summary>
/// Fetches a new buffer.
@ -29,7 +38,7 @@ internal sealed class JxlBitReader(ReadOnlyMemory<byte> bytes)
// we don't have any more data... mark an end of stream
this.buffer = 0;
this.bufferRemainingBits = 0;
this.endOfStream = true;
this.IsEndOfStream = true;
return;
}
@ -66,7 +75,7 @@ internal sealed class JxlBitReader(ReadOnlyMemory<byte> bytes)
Debug.Assert(n <= 64, "Too many bits to pack into ulong");
this.MaybeRefill();
if (this.endOfStream)
if (this.IsEndOfStream)
{
JxlThrowHelper.ThrowEndOfStream();
}
@ -111,7 +120,7 @@ internal sealed class JxlBitReader(ReadOnlyMemory<byte> bytes)
Debug.Assert(n <= 32, "Too many bits to pack into uint");
this.MaybeRefill();
if (this.endOfStream)
if (this.IsEndOfStream)
{
JxlThrowHelper.ThrowEndOfStream();
}
@ -157,11 +166,11 @@ internal sealed class JxlBitReader(ReadOnlyMemory<byte> bytes)
public void SkipBits32(uint bits) => _ = this.ReadBits32(bits);
public ulong ReadBits64(uint bits) => this.ReadBits64Core(bits, peek: false);
public ulong ReadBits64(ulong bits) => this.ReadBits64Core((uint)bits, peek: false);
public ulong PeekBits64(uint bits) => this.ReadBits64Core(bits, peek: true);
public ulong PeekBits64(ulong bits) => this.ReadBits64Core((uint)bits, peek: true);
public void SkipBits64(uint bits) => _ = this.ReadBits64(bits);
public void SkipBits64(ulong bits) => _ = this.ReadBits64(bits);
public bool ReadBoolean() => this.ReadBits32Core(1, peek: false) == 1;
}

2
src/ImageSharp/Formats/Jxl/Metadata/JxlBitDepth.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jxl.IO;
using SixLabors.ImageSharp.Formats.Jxl.Fields;
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata;

2
src/ImageSharp/Formats/Jxl/Metadata/JxlCustomTransformData.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jxl.IO;
using SixLabors.ImageSharp.Formats.Jxl.Fields;
namespace SixLabors.ImageSharp.Formats.Jxl.Metadata;

Loading…
Cancel
Save