mirror of https://github.com/SixLabors/ImageSharp
16 changed files with 1046 additions and 9 deletions
@ -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; |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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++; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue