mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
252 changed files with 29623 additions and 4 deletions
@ -0,0 +1,27 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Cms; |
|||
|
|||
/// <summary>
|
|||
/// RGB primaries for CIEXY
|
|||
/// </summary>
|
|||
internal struct JxlCieXyPrimaries |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the R component
|
|||
/// </summary>
|
|||
public CieXyChromaticityCoordinates R { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the G component
|
|||
/// </summary>
|
|||
public CieXyChromaticityCoordinates G { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the B component
|
|||
/// </summary>
|
|||
public CieXyChromaticityCoordinates B { get; set; } |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Cms; |
|||
|
|||
internal sealed class JxlColorEncoding |
|||
{ |
|||
public JxlWhitePoint WhitePoint { get; set; } = JxlWhitePoint.D65; |
|||
|
|||
public JxlPrimaries Primaries { get; set; } = JxlPrimaries.SRgb; |
|||
|
|||
public JxlRenderingIntent RenderingIntent { get; set; } = JxlRenderingIntent.Relative; |
|||
|
|||
public bool HaveFields { get; set; } = true; |
|||
|
|||
public JxlIccBytes? Icc { get; set; } |
|||
|
|||
public JxlColorSpace ColorSpace { get; set; } = JxlColorSpace.Rgb; |
|||
|
|||
public bool Cmyk { get; set; } |
|||
|
|||
public JxlCustomTransferFunction TransferFunction { get; set; } |
|||
|
|||
public JxlCustomXy White { get; set; } |
|||
|
|||
public JxlCustomXy Red { get; set; } |
|||
|
|||
public JxlCustomXy Green { get; set; } |
|||
|
|||
public JxlCustomXy Blue { get; set; } |
|||
|
|||
public bool HasPrimaries => this.ColorSpace is not (JxlColorSpace.Gray or JxlColorSpace.Xyb); |
|||
|
|||
public int Channels => (this.ColorSpace == JxlColorSpace.Gray) ? 1 : 3; |
|||
|
|||
public bool TryGetPrimaries(out JxlCieXyPrimaries xy) |
|||
{ |
|||
xy = default; |
|||
|
|||
if (!this.HasPrimaries || !this.HasPrimaries) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
switch (this.Primaries) |
|||
{ |
|||
case JxlPrimaries.Custom: |
|||
xy.R = this.Red.GetValue(); |
|||
xy.G = this.Green.GetValue(); |
|||
xy.B = this.Blue.GetValue(); |
|||
break; |
|||
|
|||
case JxlPrimaries.SRgb: |
|||
xy.R = new(0.639998686f, 0.330010138f); |
|||
xy.G = new(0.300003784f, 0.600003357f); |
|||
xy.B = new(0.150002046f, 0.059997204f); |
|||
break; |
|||
|
|||
case JxlPrimaries.Bt2020: |
|||
xy.R = new(0.708f, 0.292f); |
|||
xy.G = new(0.170f, 0.797f); |
|||
xy.B = new(0.131f, 0.046f); |
|||
break; |
|||
|
|||
case JxlPrimaries.P3: |
|||
xy.R = new(0.680f, 0.320f); |
|||
xy.G = new(0.265f, 0.690f); |
|||
xy.B = new(0.150f, 0.060f); |
|||
break; |
|||
|
|||
default: |
|||
throw new InvalidOperationException("Invalid primaries: " + this.Primaries); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Cms; |
|||
|
|||
/// <summary>
|
|||
/// Supported, JPEG XL-specific color space types.
|
|||
/// </summary>
|
|||
internal enum JxlColorSpace : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Trichromatic color data. This also includes CMYK if Black
|
|||
/// ExtraChannelInfo is present.
|
|||
/// </summary>
|
|||
Rgb, |
|||
|
|||
/// <summary>
|
|||
/// Single-channel data.
|
|||
/// </summary>
|
|||
Gray, |
|||
|
|||
/// <summary>
|
|||
/// Like Rgb but fixed values for primaries.
|
|||
/// </summary>
|
|||
Xyb, |
|||
|
|||
/// <summary>
|
|||
/// Unknown color space
|
|||
/// </summary>
|
|||
Unknown |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Cms; |
|||
|
|||
internal struct JxlCustomTransferFunction |
|||
{ |
|||
private const uint MaxGamma = 8192; |
|||
private const uint GammaMultiplier = 10000000; |
|||
|
|||
public JxlCustomTransferFunction() |
|||
{ |
|||
} |
|||
|
|||
public bool HaveGamma { get; set; } |
|||
|
|||
public uint Gamma { get; set; } |
|||
|
|||
public JxlTransferFunction TransferFunction { get; set; } = JxlTransferFunction.SRgb; |
|||
|
|||
public readonly bool IsUnknown => !this.HaveGamma && this.TransferFunction == JxlTransferFunction.Unknown; |
|||
|
|||
public readonly bool IsSrgb => !this.HaveGamma && this.TransferFunction == JxlTransferFunction.SRgb; |
|||
|
|||
public readonly bool IsLinear => !this.HaveGamma && this.TransferFunction == JxlTransferFunction.Linear; |
|||
|
|||
public readonly bool IsPq => !this.HaveGamma && this.TransferFunction == JxlTransferFunction.Pq; |
|||
|
|||
public readonly bool IsHlg => !this.HaveGamma && this.TransferFunction == JxlTransferFunction.Hlg; |
|||
|
|||
public readonly bool Is709 => !this.HaveGamma && this.TransferFunction == JxlTransferFunction.Bt709; |
|||
|
|||
public readonly bool IsDci => !this.HaveGamma && this.TransferFunction == JxlTransferFunction.Dci; |
|||
|
|||
public readonly JxlTransferFunction GetTransferFunction() |
|||
{ |
|||
if (this.HaveGamma) |
|||
{ |
|||
return JxlTransferFunction.Unknown; |
|||
} |
|||
|
|||
return this.TransferFunction; |
|||
} |
|||
|
|||
public void SetTransferFunction(JxlTransferFunction tf) |
|||
{ |
|||
this.HaveGamma = false; |
|||
this.TransferFunction = tf; |
|||
} |
|||
|
|||
public readonly float GetGamma() |
|||
{ |
|||
if (!this.HaveGamma) |
|||
{ |
|||
return 0.0f; |
|||
} |
|||
|
|||
return this.Gamma * (1.0f / GammaMultiplier); |
|||
} |
|||
|
|||
public void SetGamma(float newGamma) |
|||
{ |
|||
if (newGamma is < 1.0f / MaxGamma or > 1.0f) |
|||
{ |
|||
throw new InvalidOperationException($"Invalid gamma {newGamma}"); |
|||
} |
|||
|
|||
this.HaveGamma = false; |
|||
|
|||
if (IsAlmostEqual(newGamma, 1.0f)) |
|||
{ |
|||
this.TransferFunction = JxlTransferFunction.Linear; |
|||
return; |
|||
} |
|||
|
|||
if (IsAlmostEqual(newGamma, 1.0f / 2.6f)) |
|||
{ |
|||
this.TransferFunction = JxlTransferFunction.Dci; |
|||
return; |
|||
} |
|||
|
|||
// Don't translate 0.45.. to kSRGB nor k709 - that might change pixel
|
|||
// values because those curves also have a linear part.
|
|||
this.HaveGamma = true; |
|||
this.Gamma = (uint)MathF.Round((float)(newGamma * GammaMultiplier)); |
|||
this.TransferFunction = JxlTransferFunction.Unknown; |
|||
} |
|||
|
|||
public readonly bool IsSame(JxlCustomTransferFunction other) |
|||
{ |
|||
if (this.HaveGamma != other.HaveGamma) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (this.HaveGamma) |
|||
{ |
|||
return this.Gamma == other.Gamma; |
|||
} |
|||
|
|||
return this.TransferFunction == other.TransferFunction; |
|||
} |
|||
|
|||
private static bool IsAlmostEqual(float a, float b) |
|||
{ |
|||
const float dist = 1e-3f; |
|||
return MathF.Abs(a - b) < dist; |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Cms; |
|||
|
|||
/// <summary>
|
|||
/// A serializable form of CieXyChromaticityCoordinates
|
|||
/// </summary>
|
|||
internal struct JxlCustomXy |
|||
{ |
|||
private const uint Multiplier = 1000000; |
|||
private const float RoughLimit = 4.0f; |
|||
private const int Min = -0x200000; |
|||
private const int Max = 0x1FFFFF; |
|||
|
|||
public int X { get; set; } |
|||
|
|||
public int Y { get; set; } |
|||
|
|||
public readonly CieXyChromaticityCoordinates GetValue() => new( |
|||
x: this.X * (1.0f / Multiplier), |
|||
y: this.Y * (1.0f / Multiplier)); |
|||
|
|||
public bool SetValue(CieXyChromaticityCoordinates xy) |
|||
{ |
|||
bool ok = (Math.Abs(xy.X) < RoughLimit) && (Math.Abs(xy.Y) < RoughLimit); |
|||
|
|||
if (!ok) |
|||
{ |
|||
throw new InvalidOperationException("X or Y is out of bounds"); |
|||
} |
|||
|
|||
this.X = (int)MathF.Round((float)(xy.X * Multiplier)); |
|||
|
|||
if (this.X is < Min or > Max) |
|||
{ |
|||
throw new InvalidOperationException("X is out of bounds"); |
|||
} |
|||
|
|||
this.Y = (int)MathF.Round((float)(xy.Y * Multiplier)); |
|||
|
|||
if (this.Y is < Min or > Max) |
|||
{ |
|||
throw new InvalidOperationException("Y is out of bounds"); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public readonly bool IsSame(CieXyChromaticityCoordinates other) => this.X == other.X && this.Y == other.Y; |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Cms; |
|||
|
|||
/// <summary>
|
|||
/// Opsin constants used by the color management system
|
|||
/// </summary>
|
|||
internal static class JxlOpsinConstants |
|||
{ |
|||
public const float BScale = 1f; |
|||
|
|||
// The following constants are used for XYB.
|
|||
// They can be adjusted to change how Y<->B ratio
|
|||
// works. For example, YToBRatio works better
|
|||
// with 0.50017729543783418.
|
|||
public const float YToBRatio = 1f; |
|||
public const float BToYRatio = 1f / YToBRatio; |
|||
|
|||
// Adjusting these constants influences the opsin absorbance.
|
|||
public const float OpsinAbsorbanceBias0 = 0.0037930732552754493f; |
|||
public const float OpsinAbsorbanceBias1 = OpsinAbsorbanceBias0; |
|||
public const float OpsinAbsorbanceBias2 = OpsinAbsorbanceBias0; |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Cms; |
|||
|
|||
/// <summary>
|
|||
/// JPEG XL primaries
|
|||
/// </summary>
|
|||
internal enum JxlPrimaries : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Same as ITU-R BT.709
|
|||
/// </summary>
|
|||
SRgb = 1, |
|||
|
|||
/// <summary>
|
|||
/// Values encoded in separate fields
|
|||
/// </summary>
|
|||
Custom = 2, |
|||
|
|||
/// <summary>
|
|||
/// ITU-R BT.2020
|
|||
/// </summary>
|
|||
Bt2020 = 9, |
|||
|
|||
P3 = 11, |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Cms; |
|||
|
|||
internal enum JxlRenderingIntent : byte |
|||
{ |
|||
// Values match ICC sRGB encodings
|
|||
Perceptual, // Good for photos, requires a profile with LUT
|
|||
Relative, // Good for logos
|
|||
Saturation, // Perhaps useful for CG with fully saturated colors
|
|||
Absolute, // Leaves white point unchanged; good for proofing
|
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Cms; |
|||
|
|||
/// <summary>
|
|||
/// JPEG XL transfer function type
|
|||
/// </summary>
|
|||
internal enum JxlTransferFunction : byte |
|||
{ |
|||
/// <summary>
|
|||
/// ITU-R BT.709
|
|||
/// </summary>
|
|||
Bt709 = 1, |
|||
|
|||
/// <summary>
|
|||
/// Unknown transfer function
|
|||
/// </summary>
|
|||
Unknown = 2, |
|||
|
|||
/// <summary>
|
|||
/// Linear transfer function
|
|||
/// </summary>
|
|||
Linear = 8, |
|||
|
|||
/// <summary>
|
|||
/// sRGB
|
|||
/// </summary>
|
|||
SRgb = 13, |
|||
|
|||
/// <summary>
|
|||
/// From ITU-R BT.2100
|
|||
/// </summary>
|
|||
Pq = 16, |
|||
|
|||
/// <summary>
|
|||
/// From SMPTE RP 431-2 reference projector
|
|||
/// </summary>
|
|||
Dci = 17, |
|||
|
|||
/// <summary>
|
|||
/// From ITU-R BT.2100
|
|||
/// </summary>
|
|||
Hlg = 18, |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Cms; |
|||
|
|||
/// <summary>
|
|||
/// White point from CICP Color Primaries.
|
|||
/// </summary>
|
|||
// Note that we define a separate enum instead of using the ColorPrimaries
|
|||
// enum from CICP code because JPEG XL doesn't support all color primaries defined
|
|||
// by CICP.
|
|||
internal enum JxlWhitePoint : byte |
|||
{ |
|||
/// <summary>
|
|||
/// sRGB/ITU-R BT.709/Display P3/ITU-R BT.2020
|
|||
/// </summary>
|
|||
D65 = 1, |
|||
|
|||
/// <summary>
|
|||
/// Actual values encoded in separate fields
|
|||
/// </summary>
|
|||
Custom = 2, |
|||
|
|||
/// <summary>
|
|||
/// XYZ
|
|||
/// </summary>
|
|||
E = 10, |
|||
|
|||
/// <summary>
|
|||
/// DCI-P3
|
|||
/// </summary>
|
|||
Dci = 11, |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
# CMS |
|||
This is the JPEG XL Color Management System component. |
|||
|
|||
Not to be confused with Content Management System. |
|||
@ -0,0 +1,17 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
/// <summary>
|
|||
/// Abstracts enumeration of all fields into a visitor.
|
|||
/// </summary>
|
|||
internal interface IJxlFields |
|||
{ |
|||
/// <summary>
|
|||
/// Visits all fields into the specified JXL visitor.
|
|||
/// </summary>
|
|||
/// <param name="visitor">The visitor to use to visit all fields.</param>
|
|||
/// <returns>Status of the visit operation.</returns>
|
|||
public bool Visit(JxlVisitor visitor); |
|||
} |
|||
@ -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,39 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
|||
|
|||
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.
|
|||
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.Processing.Decoder; |
|||
|
|||
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,69 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
|||
|
|||
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>
|
|||
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>
|
|||
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,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
internal static class JxlFieldExpressions |
|||
{ |
|||
public static JxlU32Distribution Value(uint value) |
|||
{ |
|||
const uint directConstant = JxlU32Distribution.DirectConstant; |
|||
|
|||
return new(value | directConstant); |
|||
} |
|||
|
|||
public static JxlU32Distribution BitsOffset(uint bits, uint offset) |
|||
=> new(((bits - 1u) & 0x1Fu) + ((offset & 0x3FFFFFFu) << 5)); |
|||
|
|||
public static JxlU32Distribution Bits(uint value) => BitsOffset(value, 0u); |
|||
|
|||
public static int MakeBit(int index) => 1 << index; |
|||
} |
|||
@ -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,121 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
|||
|
|||
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) |
|||
{ |
|||
ulong idxExtension = JxlMath.Num0BitsBelowLS1Bit_Nonzero(remainingExtensions); |
|||
if (!this.U64(0, ref this.extensionBits[idxExtension])) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!JxlMath.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 (!JxlMath.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 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,126 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
|||
|
|||
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>
|
|||
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 - JxlMath.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,17 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
internal struct JxlU32Distribution(uint d) |
|||
{ |
|||
public const uint DirectConstant = 0x80000000u; |
|||
|
|||
public readonly bool IsDirect => (d & DirectConstant) != 0; |
|||
|
|||
public readonly uint Direct => d & (DirectConstant - 1u); |
|||
|
|||
public readonly uint ExtraBits => (d & 0x1Fu) + 1u; |
|||
|
|||
public readonly uint Offset => (d >> 5) & 0x3FFFFFF; |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
internal readonly struct JxlU32Enc |
|||
{ |
|||
private readonly InlineArray4<JxlU32Distribution> d = default; |
|||
|
|||
public JxlU32Enc(JxlU32Distribution d0, JxlU32Distribution d1, JxlU32Distribution d2, JxlU32Distribution d3) |
|||
{ |
|||
this.d[0] = d0; |
|||
this.d[1] = d1; |
|||
this.d[2] = d2; |
|||
this.d[3] = d3; |
|||
} |
|||
|
|||
public JxlU32Distribution GetDistribution(int selector) |
|||
{ |
|||
// This stuff is internal, so if argument check
|
|||
// fails it's not a user error.
|
|||
DebugGuard.MustBeLessThan(selector, 4, nameof(selector)); |
|||
|
|||
return this.d[selector]; |
|||
} |
|||
} |
|||
@ -0,0 +1,106 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; |
|||
|
|||
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,76 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
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) |
|||
{ |
|||
if (!(!this.extensionStates.IsBegun || this.extensionStates.IsEnded)) |
|||
{ |
|||
throw new InvalidOperationException("Invalid extension state"); |
|||
} |
|||
} |
|||
|
|||
this.extensionStates.Pop(); |
|||
|
|||
if (this.depth == 0) |
|||
{ |
|||
throw new InvalidOperationException("Depth must not be 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; |
|||
} |
|||
|
|||
if (bits > 1u) |
|||
{ |
|||
throw new InvalidOperationException("Invalid bits"); |
|||
} |
|||
|
|||
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; |
|||
} |
|||
} |
|||
@ -0,0 +1,320 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers.Binary; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
/// <summary>
|
|||
/// Reads primitives from streams with correct endianness.
|
|||
/// </summary>
|
|||
// TODO: move this class into the IO or Common folder?
|
|||
internal static class BinaryUtils |
|||
{ |
|||
/// <summary>
|
|||
/// Reads a <see cref="Int16" />
|
|||
/// from the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int16" /> will be read from.</param>
|
|||
/// <returns><see cref="Int16" /></returns>
|
|||
public static Int16 ReadInt16LittleEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int16)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadInt16LittleEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a <see cref="Int16" />
|
|||
/// from the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int16" /> will be read from.</param>
|
|||
/// <returns><see cref="Int16" /></returns>
|
|||
public static Int16 ReadInt16BigEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int16)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadInt16BigEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="Int16" />
|
|||
/// into the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int16" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteInt16LittleEndian(Stream stream, Int16 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int16)]; |
|||
BinaryPrimitives.WriteInt16LittleEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="Int16" />
|
|||
/// into the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int16" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteInt16BigEndian(Stream stream, Int16 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int16)]; |
|||
BinaryPrimitives.WriteInt16BigEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
/// <summary>
|
|||
/// Reads a <see cref="UInt16" />
|
|||
/// from the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt16" /> will be read from.</param>
|
|||
/// <returns><see cref="UInt16" /></returns>
|
|||
public static UInt16 ReadUInt16LittleEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt16)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadUInt16LittleEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a <see cref="UInt16" />
|
|||
/// from the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt16" /> will be read from.</param>
|
|||
/// <returns><see cref="UInt16" /></returns>
|
|||
public static UInt16 ReadUInt16BigEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt16)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadUInt16BigEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="UInt16" />
|
|||
/// into the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt16" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteUInt16LittleEndian(Stream stream, UInt16 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt16)]; |
|||
BinaryPrimitives.WriteUInt16LittleEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="UInt16" />
|
|||
/// into the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt16" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteUInt16BigEndian(Stream stream, UInt16 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt16)]; |
|||
BinaryPrimitives.WriteUInt16BigEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
/// <summary>
|
|||
/// Reads a <see cref="Int32" />
|
|||
/// from the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int32" /> will be read from.</param>
|
|||
/// <returns><see cref="Int32" /></returns>
|
|||
public static Int32 ReadInt32LittleEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int32)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadInt32LittleEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a <see cref="Int32" />
|
|||
/// from the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int32" /> will be read from.</param>
|
|||
/// <returns><see cref="Int32" /></returns>
|
|||
public static Int32 ReadInt32BigEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int32)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadInt32BigEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="Int32" />
|
|||
/// into the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int32" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteInt32LittleEndian(Stream stream, Int32 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int32)]; |
|||
BinaryPrimitives.WriteInt32LittleEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="Int32" />
|
|||
/// into the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int32" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteInt32BigEndian(Stream stream, Int32 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int32)]; |
|||
BinaryPrimitives.WriteInt32BigEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
/// <summary>
|
|||
/// Reads a <see cref="UInt32" />
|
|||
/// from the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt32" /> will be read from.</param>
|
|||
/// <returns><see cref="UInt32" /></returns>
|
|||
public static UInt32 ReadUInt32LittleEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt32)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadUInt32LittleEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a <see cref="UInt32" />
|
|||
/// from the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt32" /> will be read from.</param>
|
|||
/// <returns><see cref="UInt32" /></returns>
|
|||
public static UInt32 ReadUInt32BigEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt32)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadUInt32BigEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="UInt32" />
|
|||
/// into the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt32" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteUInt32LittleEndian(Stream stream, UInt32 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt32)]; |
|||
BinaryPrimitives.WriteUInt32LittleEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="UInt32" />
|
|||
/// into the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt32" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteUInt32BigEndian(Stream stream, UInt32 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt32)]; |
|||
BinaryPrimitives.WriteUInt32BigEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
/// <summary>
|
|||
/// Reads a <see cref="Int64" />
|
|||
/// from the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int64" /> will be read from.</param>
|
|||
/// <returns><see cref="Int64" /></returns>
|
|||
public static Int64 ReadInt64LittleEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int64)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadInt64LittleEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a <see cref="Int64" />
|
|||
/// from the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int64" /> will be read from.</param>
|
|||
/// <returns><see cref="Int64" /></returns>
|
|||
public static Int64 ReadInt64BigEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int64)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadInt64BigEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="Int64" />
|
|||
/// into the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int64" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteInt64LittleEndian(Stream stream, Int64 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int64)]; |
|||
BinaryPrimitives.WriteInt64LittleEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="Int64" />
|
|||
/// into the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="Int64" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteInt64BigEndian(Stream stream, Int64 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(Int64)]; |
|||
BinaryPrimitives.WriteInt64BigEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
/// <summary>
|
|||
/// Reads a <see cref="UInt64" />
|
|||
/// from the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt64" /> will be read from.</param>
|
|||
/// <returns><see cref="UInt64" /></returns>
|
|||
public static UInt64 ReadUInt64LittleEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt64)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadUInt64LittleEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a <see cref="UInt64" />
|
|||
/// from the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt64" /> will be read from.</param>
|
|||
/// <returns><see cref="UInt64" /></returns>
|
|||
public static UInt64 ReadUInt64BigEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt64)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.ReadUInt64BigEndian(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="UInt64" />
|
|||
/// into the specified stream in little-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt64" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteUInt64LittleEndian(Stream stream, UInt64 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt64)]; |
|||
BinaryPrimitives.WriteUInt64LittleEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a <see cref="UInt64" />
|
|||
/// into the specified stream in big-endian order.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream where the <see cref="UInt64" /> will be written to.</param>
|
|||
/// <param name="value">Value which will be written to the stream.</param>
|
|||
public static void WriteUInt64BigEndian(Stream stream, UInt64 value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(UInt64)]; |
|||
BinaryPrimitives.WriteUInt64BigEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
<#@ template language="C#" #> |
|||
<#@ import namespace="System" #> |
|||
<#@ import namespace="System.IO" #> |
|||
<#@ import namespace="System.Collections.Generic" #> |
|||
<#@ output extension=".Generated.cs" #> |
|||
// Copyright (c) Six Labors. |
|||
// Licensed under the Six Labors Split License. |
|||
|
|||
using System.Buffers.Binary; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
<# |
|||
List<Type> types = [ |
|||
typeof(short), |
|||
typeof(ushort), |
|||
typeof(int), |
|||
typeof(uint), |
|||
typeof(long), |
|||
typeof(ulong) |
|||
]; |
|||
#> |
|||
/// <summary> |
|||
/// Reads primitives from streams with correct endianness. |
|||
/// </summary> |
|||
// TODO: move this class into the IO or Common folder? |
|||
internal static class BinaryUtils |
|||
{ |
|||
<# |
|||
foreach (Type type in types) |
|||
{ |
|||
#> |
|||
/// <summary> |
|||
/// Reads a <see cref="<#= type.Name #>" /> |
|||
/// from the specified stream in little-endian order. |
|||
/// </summary> |
|||
/// <param name="stream">The stream where the <see cref="<#= type.Name #>" /> will be read from.</param> |
|||
/// <returns><see cref="<#= type.Name #>" /></returns> |
|||
public static <#= type.Name #> Read<#= type.Name#>LittleEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(<#= type.Name #>)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.Read<#= type.Name #>LittleEndian(data); |
|||
} |
|||
|
|||
/// <summary> |
|||
/// Reads a <see cref="<#= type.Name #>" /> |
|||
/// from the specified stream in big-endian order. |
|||
/// </summary> |
|||
/// <param name="stream">The stream where the <see cref="<#= type.Name #>" /> will be read from.</param> |
|||
/// <returns><see cref="<#= type.Name #>" /></returns> |
|||
public static <#= type.Name #> Read<#= type.Name#>BigEndian(Stream stream) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(<#= type.Name #>)]; |
|||
stream.ReadExactly(data); |
|||
return BinaryPrimitives.Read<#= type.Name #>BigEndian(data); |
|||
} |
|||
|
|||
/// <summary> |
|||
/// Writes a <see cref="<#= type.Name #>" /> |
|||
/// into the specified stream in little-endian order. |
|||
/// </summary> |
|||
/// <param name="stream">The stream where the <see cref="<#= type.Name #>" /> will be written to.</param> |
|||
/// <param name="value">Value which will be written to the stream.</param> |
|||
public static void Write<#= type.Name #>LittleEndian(Stream stream, <#= type.Name #> value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(<#= type.Name #>)]; |
|||
BinaryPrimitives.Write<#= type.Name #>LittleEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
|
|||
/// <summary> |
|||
/// Writes a <see cref="<#= type.Name #>" /> |
|||
/// into the specified stream in big-endian order. |
|||
/// </summary> |
|||
/// <param name="stream">The stream where the <see cref="<#= type.Name #>" /> will be written to.</param> |
|||
/// <param name="value">Value which will be written to the stream.</param> |
|||
public static void Write<#= type.Name #>BigEndian(Stream stream, <#= type.Name #> value) |
|||
{ |
|||
Span<byte> data = stackalloc byte[sizeof(<#= type.Name #>)]; |
|||
BinaryPrimitives.Write<#= type.Name #>BigEndian(data, value); |
|||
stream.Write(data); |
|||
} |
|||
<# } #> |
|||
} |
|||
@ -0,0 +1,158 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers.Binary; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Container; |
|||
|
|||
/// <summary>
|
|||
/// Header for JPEG XL container format.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential, Size = 16)] |
|||
internal struct JxlBoxHeader |
|||
{ |
|||
/// <summary>
|
|||
/// Box size in bytes.
|
|||
/// </summary>
|
|||
public ulong Size; |
|||
|
|||
/// <summary>
|
|||
/// Type of the box.
|
|||
/// </summary>
|
|||
public uint Type; |
|||
|
|||
/// <summary>
|
|||
/// True if the size field extends until the end of the file.
|
|||
/// </summary>
|
|||
public bool SizeExtendsTillEnd; |
|||
|
|||
/// <summary>
|
|||
/// True if the size is 64-bit.
|
|||
/// </summary>
|
|||
public bool ContainsLargeSize; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JxlBoxHeader"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="size">The size of the box.</param>
|
|||
/// <param name="type">The type of the box.</param>
|
|||
/// <param name="sizeExtendsTillEnd">Does the box size extend till the end of the file?</param>
|
|||
/// <param name="containsLargeSize">Is there a 64-bit size field?</param>
|
|||
public JxlBoxHeader(ulong size, uint type, bool sizeExtendsTillEnd, bool containsLargeSize) |
|||
{ |
|||
this.Size = size; |
|||
this.Type = type; |
|||
this.SizeExtendsTillEnd = sizeExtendsTillEnd; |
|||
this.ContainsLargeSize = containsLargeSize; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a 4-character ASCII string (e.g. "jxlc") into a uint type code.
|
|||
/// </summary>
|
|||
/// <param name="typeString">Input type string to convert</param>
|
|||
/// <returns>Unsigned integer representation of the type string</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static uint TypeFromString(string typeString) |
|||
{ |
|||
if (typeString.Length != 4) |
|||
{ |
|||
throw new ArgumentException("Box type must be exactly 4 characters", nameof(typeString)); |
|||
} |
|||
|
|||
return ((uint)typeString[0] << 24) | |
|||
((uint)typeString[1] << 16) | |
|||
((uint)typeString[2] << 8) | |
|||
typeString[3]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a uint type code back into a 4-character ASCII string.
|
|||
/// </summary>
|
|||
/// <param name="typeCode">Unsigned integer representation of the type string</param>
|
|||
/// <returns>The string representing the type code.</returns>
|
|||
public static string TypeToString(uint typeCode) |
|||
{ |
|||
return typeCode switch |
|||
{ |
|||
0x6A786C20 => "jxl ", |
|||
0x6A786C70 => "jxlp", |
|||
0x6A786C63 => "jxlc", |
|||
0x66747970 => "ftyp", |
|||
0x6A627264 => "jbrd", |
|||
0x45786966 => "Exif", |
|||
0x786D6C20 => "xml ", |
|||
0x6A756D62 => "jumb", |
|||
_ => Fallback(typeCode) |
|||
}; |
|||
|
|||
static string Fallback(uint typeCode) |
|||
{ |
|||
// The box type is not known
|
|||
Span<byte> buffer = stackalloc byte[4]; |
|||
BinaryPrimitives.WriteUInt32BigEndian(buffer, typeCode); |
|||
|
|||
return Encoding.ASCII.GetString(buffer); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Parses the JPEG XL box header.
|
|||
/// </summary>
|
|||
/// <param name="stream">A stream to parse the header from.</param>
|
|||
/// <returns>The box header.</returns>
|
|||
/// <exception cref="InvalidOperationException">Thrown when the header is invalid.</exception>
|
|||
public static JxlBoxHeader ReadHeader(Stream stream) |
|||
{ |
|||
ulong size = BinaryUtils.ReadUInt32BigEndian(stream); |
|||
bool haveSize64 = false; |
|||
|
|||
if (size == 1) |
|||
{ |
|||
// When the size value is equal to 1, a new 64-bit
|
|||
// size field follows.
|
|||
haveSize64 = true; |
|||
size = BinaryUtils.ReadUInt64BigEndian(stream); |
|||
} |
|||
|
|||
// Read the 4-byte type field.
|
|||
uint type = BinaryUtils.ReadUInt32BigEndian(stream); |
|||
|
|||
if (haveSize64) |
|||
{ |
|||
// When the 64-bit largesize was read,
|
|||
// the size cannot proceed till the end of the file.
|
|||
if (size is 0 or 1) |
|||
{ |
|||
throw new InvalidOperationException("Large size cannot have another large size or extend till the end of the file"); |
|||
} |
|||
|
|||
return new JxlBoxHeader(size, type, sizeExtendsTillEnd: false, containsLargeSize: true); |
|||
} |
|||
else |
|||
{ |
|||
return new JxlBoxHeader(size, type, sizeExtendsTillEnd: size == 0, containsLargeSize: false); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the box header to the specified stream.
|
|||
/// </summary>
|
|||
/// <param name="writer">The stream to write the box header to.</param>
|
|||
public readonly void WriteHeader(Stream writer) |
|||
{ |
|||
if (this.Size is > uint.MaxValue or 1) |
|||
{ |
|||
BinaryUtils.WriteUInt32BigEndian(writer, 1); // Indicates a large size is present
|
|||
BinaryUtils.WriteUInt64BigEndian(writer, this.Size); |
|||
} |
|||
else |
|||
{ |
|||
BinaryUtils.WriteUInt32BigEndian(writer, (uint)this.Size); |
|||
} |
|||
|
|||
BinaryUtils.WriteUInt32BigEndian(writer, this.Type); |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Container; |
|||
|
|||
/// <summary>
|
|||
/// A ftyp box payload.
|
|||
/// </summary>
|
|||
internal sealed class JxlFileTypeBox(string majorBrand, uint minorVersion) |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the primary format. Has to be "jxl " for JPEG XL.
|
|||
/// </summary>
|
|||
public string MajorBrand { get; set; } = majorBrand; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the revision of the major brand.
|
|||
/// </summary>
|
|||
public uint MinorVersion { get; set; } = minorVersion; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the list of other brands the file is compatible with.
|
|||
/// </summary>
|
|||
public List<string> CompatibleBrands { get; set; } = []; |
|||
|
|||
public int GetPayloadSize() => 8 + (this.CompatibleBrands.Count * 4); |
|||
|
|||
public static JxlFileTypeBox Parse(Stream stream, ulong boxSize) |
|||
{ |
|||
string majorBrand = JxlBoxHeader.TypeToString(BinaryUtils.ReadUInt32BigEndian(stream)); |
|||
uint minorVersion = BinaryUtils.ReadUInt32BigEndian(stream); |
|||
boxSize -= 8; |
|||
|
|||
List<string> compatibleBrands = []; |
|||
for (ulong i = 0; i < boxSize; i += 4) |
|||
{ |
|||
compatibleBrands.Add(JxlBoxHeader.TypeToString(BinaryUtils.ReadUInt32BigEndian(stream))); |
|||
} |
|||
|
|||
JxlFileTypeBox ftyp = new(majorBrand, minorVersion) |
|||
{ |
|||
CompatibleBrands = compatibleBrands |
|||
}; |
|||
|
|||
return ftyp; |
|||
} |
|||
|
|||
public void WritePayload(Stream stream) |
|||
{ |
|||
BinaryUtils.WriteUInt32BigEndian(stream, JxlBoxHeader.TypeFromString(this.MajorBrand)); |
|||
BinaryUtils.WriteUInt32BigEndian(stream, this.MinorVersion); |
|||
|
|||
foreach (string compatibleBrand in this.CompatibleBrands) |
|||
{ |
|||
BinaryUtils.WriteUInt32BigEndian(stream, JxlBoxHeader.TypeFromString(compatibleBrand)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; |
|||
|
|||
internal static class JxlAnsConstants |
|||
{ |
|||
public const int AnsLogTableSize = 12; |
|||
public const int AnsTableSize = 1 << AnsLogTableSize; |
|||
public const int AnsTabMask = AnsTableSize - 1; |
|||
public const int PrefixMaxAlphabetSize = 4096; |
|||
public const int AnsMaxAlphabetSize = 256; |
|||
public const int PrefixMaxBits = 15; |
|||
public const int AnsSignature = 0x13; |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct JxlAnsEntry |
|||
{ |
|||
public byte Cutoff; |
|||
public byte RightValue; |
|||
public ushort Frequency0; |
|||
public ushort Offsets1; |
|||
public ushort Frequency1XorFrequency0; |
|||
} |
|||
@ -0,0 +1,239 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; |
|||
|
|||
internal static class JxlAnsHelper |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetPopulationCountPrecision(int logCount, int shift) |
|||
=> Math.Max(0, Math.Min(logCount, shift - ((JxlAnsConstants.AnsLogTableSize - logCount) >> 1))); |
|||
|
|||
// NOTE: The result may potentially be large, so prefer using a memory allocator
|
|||
public static IMemoryOwner<uint> CreateFlatHistogram(Configuration configuration, int length, int totalCount) |
|||
{ |
|||
DebugGuard.MustBeLessThanOrEqualTo(length, 0, nameof(length)); |
|||
DebugGuard.MustBeGreaterThan(length, totalCount, nameof(length)); |
|||
|
|||
int count = totalCount / length; |
|||
IMemoryOwner<uint> result = configuration.MemoryAllocator.Allocate<uint>(length); |
|||
Span<uint> resultSpan = result.Memory.Span; |
|||
uint unsignedCount = (uint)count; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
resultSpan[i] = unsignedCount; |
|||
} |
|||
|
|||
int remCounts = totalCount % length; |
|||
for (int i = 0; i < remCounts; i++) |
|||
{ |
|||
resultSpan[i]++; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public static JxlAnsSymbol Lookup(ReadOnlySpan<JxlAnsEntry> table, int value, int logEntrySize, int entrySizeMinus1) |
|||
{ |
|||
int i = value >> logEntrySize; |
|||
int pos = value & entrySizeMinus1; |
|||
|
|||
JxlAnsEntry entry = table[i]; |
|||
|
|||
int cutoff = entry.Cutoff; |
|||
int rightValue = entry.RightValue; |
|||
int freq0 = entry.Frequency0; |
|||
|
|||
bool greater = pos >= cutoff; |
|||
|
|||
int offsets1or0 = greater ? entry.Offsets1 : 0; |
|||
int freq1xorfreq0or0 = greater ? entry.Frequency1XorFrequency0 : 0; |
|||
|
|||
JxlAnsSymbol symbol = new() |
|||
{ |
|||
Value = greater ? rightValue : i, |
|||
Offset = offsets1or0 + pos, |
|||
Frequency = freq0 ^ freq1xorfreq0or0 |
|||
}; |
|||
|
|||
return symbol; |
|||
} |
|||
|
|||
public static bool InitAliasTable(Span<int> preDistribution, uint logRange, int logAlphaSize, Span<JxlAnsEntry> entries) |
|||
{ |
|||
DebugGuard.MustBeLessThan(logAlphaSize, (int)logRange, nameof(logAlphaSize)); |
|||
|
|||
int range = 1 << (int)logRange; |
|||
int tableSize = 1 << logAlphaSize; |
|||
|
|||
int distributionPointer = preDistribution.Length - 1; |
|||
|
|||
while (distributionPointer >= 0 && preDistribution[distributionPointer] == 0) |
|||
{ |
|||
distributionPointer--; |
|||
} |
|||
|
|||
if (distributionPointer < 0) |
|||
{ |
|||
preDistribution[0] = range; |
|||
distributionPointer = 0; |
|||
} |
|||
|
|||
Span<int> distribution = preDistribution[..(distributionPointer + 1)]; |
|||
|
|||
if (distribution.Length > tableSize) |
|||
{ |
|||
throw new InvalidOperationException("Too many items in the distribution"); |
|||
} |
|||
|
|||
int entrySize = range >> logAlphaSize; |
|||
int singleSymbol = -1; |
|||
int sum = 0; |
|||
|
|||
for (int sym = 0; sym < distribution.Length; sym++) |
|||
{ |
|||
int value = distribution[sym]; |
|||
sum += value; |
|||
|
|||
if (value == JxlAnsConstants.AnsTableSize) |
|||
{ |
|||
if (singleSymbol != -1) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
singleSymbol = sym; |
|||
} |
|||
} |
|||
|
|||
if (sum != range) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (singleSymbol != -1) |
|||
{ |
|||
byte sym = (byte)singleSymbol; |
|||
if (singleSymbol != sym) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (int i = 0; i < tableSize; i++) |
|||
{ |
|||
ref JxlAnsEntry jxlEntry = ref entries[i]; |
|||
|
|||
jxlEntry.RightValue = sym; |
|||
jxlEntry.Cutoff = 0; |
|||
jxlEntry.Offsets1 = (ushort)(entrySize * i); |
|||
jxlEntry.Frequency0 = 0; |
|||
jxlEntry.Frequency1XorFrequency0 = JxlAnsConstants.AnsTableSize; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
Span<uint> underfullPosn = stackalloc uint[distribution.Length]; |
|||
Span<uint> overfullPosn = stackalloc uint[distribution.Length]; |
|||
Span<uint> cutoffs = stackalloc uint[1 << logAlphaSize]; |
|||
|
|||
int underfullPointer = 0; |
|||
int overfullPointer = 0; |
|||
|
|||
for (int i = 0; i < distribution.Length; i++) |
|||
{ |
|||
uint currentCutoff = (uint)distribution[i]; |
|||
|
|||
cutoffs[i] = currentCutoff; |
|||
|
|||
if (currentCutoff > entrySize) |
|||
{ |
|||
overfullPosn[overfullPointer] = (uint)i; |
|||
overfullPointer++; |
|||
} |
|||
else if (currentCutoff < entrySize) |
|||
{ |
|||
underfullPosn[underfullPointer] = (uint)i; |
|||
underfullPointer++; |
|||
} |
|||
} |
|||
|
|||
for (int i = distribution.Length; i < tableSize; i++) |
|||
{ |
|||
cutoffs[i] = 0; |
|||
underfullPosn[underfullPointer] = (uint)i; |
|||
underfullPointer++; |
|||
} |
|||
|
|||
uint unsignedEntrySize = (uint)entrySize; |
|||
|
|||
while (overfullPointer >= 0) |
|||
{ |
|||
uint overfullIndex = overfullPosn[overfullPointer]; |
|||
overfullPointer--; |
|||
|
|||
if (underfullPointer <= -1) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
uint underfullIndex = underfullPosn[underfullPointer]; |
|||
underfullPointer--; |
|||
|
|||
int signedOverfullIndex = (int)overfullIndex; |
|||
int signedUnderfullIndex = (int)underfullIndex; |
|||
|
|||
uint underfullBy = unsignedEntrySize - cutoffs[signedUnderfullIndex]; |
|||
cutoffs[signedOverfullIndex] -= underfullBy; |
|||
|
|||
ref JxlAnsEntry currentEntry = ref entries[signedUnderfullIndex]; |
|||
|
|||
currentEntry.RightValue = unchecked((byte)overfullIndex); |
|||
currentEntry.Offsets1 = unchecked((ushort)cutoffs[signedOverfullIndex]); |
|||
|
|||
uint currentCutoff = cutoffs[signedOverfullIndex]; |
|||
|
|||
if (currentCutoff < entrySize) |
|||
{ |
|||
underfullPosn[underfullPointer] = overfullIndex; |
|||
underfullPointer++; |
|||
} |
|||
else if (currentCutoff > entrySize) |
|||
{ |
|||
overfullPosn[overfullPointer] = overfullIndex; |
|||
overfullPointer++; |
|||
} |
|||
} |
|||
|
|||
for (uint i = 0; i < tableSize; i++) |
|||
{ |
|||
uint currentCutoff = cutoffs[(int)i]; |
|||
ref JxlAnsEntry entry = ref entries[(int)i]; |
|||
|
|||
if (currentCutoff == entrySize) |
|||
{ |
|||
entry.RightValue = (byte)i; |
|||
entry.Offsets1 = 0; |
|||
entry.Cutoff = 0; |
|||
} |
|||
else |
|||
{ |
|||
entry.Offsets1 -= (ushort)currentCutoff; |
|||
entry.Cutoff = (byte)currentCutoff; |
|||
} |
|||
|
|||
int freq0 = i < distribution.Length ? distribution[(int)i] : 0; |
|||
int i1 = entry.RightValue; |
|||
int freq1 = i1 < distribution.Length ? distribution[i1] : 0; |
|||
|
|||
entry.Frequency0 = (ushort)freq0; |
|||
entry.Frequency1XorFrequency0 = (ushort)(freq1 ^ freq0); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; |
|||
|
|||
internal sealed class JxlAnsHybridUIntConfiguration : IJxlFields |
|||
{ |
|||
public JxlAnsHybridUIntConfiguration(uint splitExponent = 4, uint msbInToken = 2, uint lsbInToken = 0) |
|||
{ |
|||
this.SplitExponent = splitExponent; |
|||
this.SplitToken = 1u << (int)splitExponent; |
|||
this.MsbInToken = msbInToken; |
|||
this.LsbInToken = lsbInToken; |
|||
|
|||
if (splitExponent < msbInToken + lsbInToken) |
|||
{ |
|||
throw new InvalidOperationException("Split exponent should be < msbInToken + lsbInToken"); |
|||
} |
|||
} |
|||
|
|||
public uint SplitExponent { get; set; } |
|||
|
|||
public uint SplitToken { get; set; } |
|||
|
|||
public uint MsbInToken { get; set; } // Most significant bit
|
|||
|
|||
public uint LsbInToken { get; set; } // Least significant bit
|
|||
|
|||
public uint LsbMask => (1u << (int)this.LsbInToken) - 1; |
|||
|
|||
public void Encode(uint value, ref uint token, ref uint bitCount, ref uint bits) |
|||
{ |
|||
if (value < this.SplitToken) |
|||
{ |
|||
token = value; |
|||
bitCount = 0; |
|||
bits = 0; |
|||
} |
|||
else |
|||
{ |
|||
uint n = JxlMath.FloorLog2Nonzero(value); |
|||
uint m = value - (1u << (int)n); |
|||
|
|||
unchecked |
|||
{ |
|||
// The following expression is quite complex.
|
|||
// See https://github.com/libjxl/libjxl/blob/main/lib/jxl/dec_ans.h#L83C16-L86C47.
|
|||
token = this.SplitToken + |
|||
(uint)(((n - this.SplitExponent) << (int)(this.MsbInToken + this.LsbInToken)) + |
|||
((m >> (int)(n - this.MsbInToken)) << (int)this.LsbInToken) + |
|||
(m & ((1 << (int)this.LsbInToken) - 1))); |
|||
|
|||
bitCount = n - this.MsbInToken - this.LsbInToken; |
|||
bits = (value >> (int)this.LsbInToken) & ((1u << (int)bitCount) - 1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,81 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; |
|||
|
|||
internal sealed class JxlAnsLz77Parameters : IJxlFields |
|||
{ |
|||
private bool enabled; |
|||
private uint minimumSymbol; |
|||
private uint minimumLength; |
|||
private JxlAnsHybridUIntConfiguration lengthUintConfig = new(0, 0, 0); |
|||
|
|||
public JxlAnsLz77Parameters() => JxlBundle.Init(this); |
|||
|
|||
public bool Enabled |
|||
{ |
|||
get => this.enabled; |
|||
set => this.enabled = value; |
|||
} |
|||
|
|||
public uint MinimumSymbol |
|||
{ |
|||
get => this.minimumSymbol; |
|||
set => this.minimumSymbol = value; |
|||
} |
|||
|
|||
public uint MinimumLength |
|||
{ |
|||
get => this.minimumLength; |
|||
set => this.minimumLength = value; |
|||
} |
|||
|
|||
public JxlAnsHybridUIntConfiguration LengthUintConfig |
|||
{ |
|||
get => this.lengthUintConfig; |
|||
set => this.lengthUintConfig = value; |
|||
} |
|||
|
|||
public int NonserializedDistanceContext { get; set; } |
|||
|
|||
public ref JxlAnsHybridUIntConfiguration GetLengthUIntConfigReference() => ref this.lengthUintConfig; |
|||
|
|||
public bool Visit(JxlVisitor visitor) |
|||
{ |
|||
if (!visitor.Boolean(false, ref this.enabled)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!visitor.Conditional(this.enabled)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(224u), |
|||
JxlFieldExpressions.Value(512u), |
|||
JxlFieldExpressions.Value(4096u), |
|||
JxlFieldExpressions.BitsOffset(15u, 8u), |
|||
224u, |
|||
ref this.minimumSymbol)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(3u), |
|||
JxlFieldExpressions.Value(4u), |
|||
JxlFieldExpressions.BitsOffset(2u, 5u), |
|||
JxlFieldExpressions.BitsOffset(8u, 9u), |
|||
3u, |
|||
ref this.minimumLength)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct JxlAnsSymbol(int value, int offset, int frequency) |
|||
{ |
|||
public int Value = value; |
|||
public int Offset = offset; |
|||
public int Frequency = frequency; |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Metadata; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
|
|||
/// <summary>
|
|||
/// Describes duration of frames that make up an animation.
|
|||
/// </summary>
|
|||
internal sealed class JxlAnimationFrame : IJxlFields |
|||
{ |
|||
/// <summary>
|
|||
/// See <see cref="Duration"/>.
|
|||
/// </summary>
|
|||
private uint duration; |
|||
|
|||
/// <summary>
|
|||
/// See <see cref="Timecode"/>.
|
|||
/// </summary>
|
|||
private uint timecode; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the duration of the animation.
|
|||
/// </summary>
|
|||
public uint Duration |
|||
{ |
|||
get => this.duration; |
|||
set => this.duration = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the timecode of the animation. The
|
|||
/// format is 0xHHMMSSFF.
|
|||
/// </summary>
|
|||
public uint Timecode |
|||
{ |
|||
get => this.timecode; |
|||
set => this.timecode = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the optional codec metadata.
|
|||
/// </summary>
|
|||
public JxlCodecMetadata? CodecMetadata { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) |
|||
{ |
|||
if (visitor.Conditional(this.CodecMetadata?.ImageMetadata?.HaveAnimation == true)) |
|||
{ |
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(0), |
|||
JxlFieldExpressions.Value(1), |
|||
JxlFieldExpressions.Bits(8), |
|||
JxlFieldExpressions.Bits(32), |
|||
0, |
|||
ref this.duration)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
if (visitor.Conditional(this.CodecMetadata?.ImageMetadata?.Animation?.ContainsTimecodes == true)) |
|||
{ |
|||
if (!visitor.Bits(32, 0u, ref this.timecode)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
|
|||
/// <summary>
|
|||
/// Represents the blending mode describing how to combine
|
|||
/// current frame with previously saved frame.
|
|||
/// </summary>
|
|||
internal enum JxlBlendMode : byte |
|||
{ |
|||
/// <summary>
|
|||
/// New values replace old ones.
|
|||
/// <code>
|
|||
/// sample = new
|
|||
/// </code>
|
|||
/// </summary>
|
|||
Replace, |
|||
|
|||
/// <summary>
|
|||
/// New values add to the old ones.
|
|||
/// <code>
|
|||
/// sample = old + new
|
|||
/// </code>
|
|||
/// </summary>
|
|||
Add, |
|||
|
|||
/// <summary>
|
|||
/// New values replace old ones if alpha>0:
|
|||
/// <code>
|
|||
/// alpha = old + new * (1 - old)
|
|||
/// </code>
|
|||
/// For other channels if !alpha_associated:
|
|||
/// <code>
|
|||
/// sample = ((1 - newAlpha) * old * oldAlpha + newAlpha * new) / alpha
|
|||
/// </code>
|
|||
/// For other channels if alpha_associated:
|
|||
/// <code>
|
|||
/// sample = (1 - newAlpha) * old + new
|
|||
/// </code>
|
|||
/// </summary>
|
|||
Blend, |
|||
|
|||
/// <summary>
|
|||
/// New values are added to the old ones if alpha>0:
|
|||
/// For the alpha channel that is used as source:
|
|||
/// <code>
|
|||
/// sample = old + new * (1 - old)
|
|||
/// </code>
|
|||
/// Otherwise:
|
|||
/// <code>
|
|||
/// sample = old + alpha * new
|
|||
/// </code>
|
|||
/// </summary>
|
|||
AlphaWeightedBlend, |
|||
|
|||
/// <summary>
|
|||
/// New values are multiplied by old ones:
|
|||
/// <code>
|
|||
/// sample = old * new
|
|||
/// </code>
|
|||
/// </summary>
|
|||
Multiply |
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
|
|||
/// <summary>
|
|||
/// Provides options and instructions that tell the decoder the proper
|
|||
/// way to blend the current and previous frame together.
|
|||
/// </summary>
|
|||
internal sealed class JxlBlendingInfo : IJxlFields |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JxlBlendingInfo"/> class.
|
|||
/// </summary>
|
|||
public JxlBlendingInfo() => JxlBundle.Init(this); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the blending mode. See <see cref="JxlBlendMode"/>.
|
|||
/// </summary>
|
|||
public JxlBlendMode BlendMode { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the value that indicates which extra channel
|
|||
/// to use as alpha channel for blending.
|
|||
/// </summary>
|
|||
public uint AlphaChannel { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the alpha or channel values
|
|||
/// must be clamped* to the 0 through 1 range.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Clamped - must be limited to the specified range.
|
|||
/// </remarks>
|
|||
public bool Clamp { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the frame ID to copy from (0 through 3).
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If <see cref="BlendMode"/> is equal to <see cref="JxlBlendMode.Replace"/>,
|
|||
/// the value of this property is ignored.
|
|||
/// </remarks>
|
|||
public uint Source { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the total number of extra channels.
|
|||
/// </summary>
|
|||
public int ExtraChannelCount { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the frame is partial.
|
|||
/// </summary>
|
|||
public bool IsPartialFrame { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) |
|||
{ |
|||
JxlBlendMode mode = this.BlendMode; |
|||
if (!VisitBlendMode(visitor, JxlBlendMode.Replace, ref mode)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.BlendMode = mode; |
|||
|
|||
if (visitor.Conditional(this.ExtraChannelCount > 0 && mode is JxlBlendMode.Blend or JxlBlendMode.AlphaWeightedBlend)) |
|||
{ |
|||
uint alphaChannel = this.AlphaChannel; |
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(0u), |
|||
JxlFieldExpressions.Value(1u), |
|||
JxlFieldExpressions.Value(2u), |
|||
JxlFieldExpressions.BitsOffset(3u, 3u), |
|||
0, |
|||
ref alphaChannel)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.AlphaChannel = alphaChannel; |
|||
|
|||
if (visitor.IsReading && alphaChannel >= this.ExtraChannelCount) |
|||
{ |
|||
throw new InvalidOperationException("Invalid alpha channel for blending"); |
|||
} |
|||
} |
|||
|
|||
if (visitor.Conditional((this.ExtraChannelCount > 0 && mode is JxlBlendMode.Blend or JxlBlendMode.AlphaWeightedBlend) || mode == JxlBlendMode.Multiply)) |
|||
{ |
|||
bool clamp = this.Clamp; |
|||
|
|||
if (!visitor.Boolean(false, ref clamp)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.Clamp = clamp; |
|||
} |
|||
|
|||
if (visitor.Conditional(mode != JxlBlendMode.Replace || this.IsPartialFrame)) |
|||
{ |
|||
uint source = this.Source; |
|||
|
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(0), |
|||
JxlFieldExpressions.Value(1), |
|||
JxlFieldExpressions.Value(2), |
|||
JxlFieldExpressions.Value(3), |
|||
0, |
|||
ref source)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.Source = source; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private static bool VisitBlendMode(JxlVisitor visitor, JxlBlendMode defaultValue, ref JxlBlendMode valueToEncode) |
|||
{ |
|||
uint unsignedBackingValue = (uint)valueToEncode; |
|||
|
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value((uint)JxlBlendMode.Replace), |
|||
JxlFieldExpressions.Value((uint)JxlBlendMode.Add), |
|||
JxlFieldExpressions.Value((uint)JxlBlendMode.Blend), |
|||
JxlFieldExpressions.BitsOffset(2u, 3u), |
|||
(uint)defaultValue, |
|||
ref unsignedBackingValue)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (unsignedBackingValue > (uint)JxlBlendMode.Multiply) |
|||
{ |
|||
throw new InvalidOperationException("Invalid blend mode"); |
|||
} |
|||
|
|||
valueToEncode = (JxlBlendMode)unsignedBackingValue; |
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
|
|||
/// <summary>
|
|||
/// Represents the type of JPEG XL color transform.
|
|||
/// </summary>
|
|||
internal enum JxlColorTransform : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Use XYB encoding
|
|||
/// </summary>
|
|||
Xyb, |
|||
|
|||
/// <summary>
|
|||
/// Encode according to the attached color profile.
|
|||
/// </summary>
|
|||
None, |
|||
|
|||
/// <summary>
|
|||
/// Encode according to the attached color profile but
|
|||
/// transformed into Y'Cb'Cr.
|
|||
/// </summary>
|
|||
YCbCr, |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
|
|||
/// <summary>
|
|||
/// Helper methods associated with JxlColorTransform.
|
|||
/// </summary>
|
|||
internal static class JxlColorTransformHelpers |
|||
{ |
|||
private static ReadOnlySpan<int> Grayscale => [0, 0, 0]; |
|||
|
|||
private static ReadOnlySpan<int> YCbCr => [1, 0, 2]; |
|||
|
|||
private static ReadOnlySpan<int> None => [0, 1, 2]; |
|||
|
|||
public static ReadOnlySpan<int> GetJpegOrder(JxlColorTransform transform, bool isGraysacle) |
|||
{ |
|||
if (isGraysacle) |
|||
{ |
|||
return Grayscale; |
|||
} |
|||
|
|||
if (transform == JxlColorTransform.YCbCr) |
|||
{ |
|||
return YCbCr; |
|||
} |
|||
|
|||
return None; |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
|
|||
/// <summary>
|
|||
/// Represents the kind of frame encoding.
|
|||
/// </summary>
|
|||
internal enum JxlFrameEncoding : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Use VarDCT
|
|||
/// </summary>
|
|||
VarDct, |
|||
|
|||
/// <summary>
|
|||
/// Use Modular encoding
|
|||
/// </summary>
|
|||
Modular |
|||
} |
|||
@ -0,0 +1,744 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
// Disable IDE0032 for consistency with other fields.
|
|||
// We have to avoid auto properties for most fields
|
|||
// so we can use the ref keyword on them directly.
|
|||
#pragma warning disable IDE0032 // Use auto property
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
using SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
|
|||
/// <summary>
|
|||
/// Control information for a JPEG XL frame.
|
|||
/// </summary>
|
|||
internal sealed class JxlFrameHeader : IJxlFields |
|||
{ |
|||
// The following are backing fields for properties.
|
|||
private JxlFrameEncoding encoding = JxlFrameEncoding.Modular; |
|||
private JxlFrameType frameType = JxlFrameType.RegularFrame; |
|||
private ulong flags; |
|||
private JxlColorTransform colorTransform = JxlColorTransform.Xyb; |
|||
private JxlYCbCrChromaSubsampling? chromaSubsampling; |
|||
private uint groupSizeShift; |
|||
private uint xQmScale; |
|||
private uint bQmScale; |
|||
private string? name; |
|||
private bool customSizeOrOrigin; |
|||
private Size frameSize; |
|||
private uint upsampling; |
|||
private List<uint> extraChannelUpsampling = []; |
|||
private Point frameOrigin; |
|||
private JxlBlendingInfo? blendingInfo; |
|||
private List<JxlBlendingInfo> extraChannelBlendingInfo = []; |
|||
private readonly JxlAnimationFrame? animationFrame; |
|||
private bool isLast; |
|||
private uint saveAsReference; |
|||
private bool saveBeforeColorTransform; |
|||
private uint dcLevel; |
|||
private JxlCodecMetadata? metadata; |
|||
private JxlLoopFilter? loopFilter; |
|||
private ulong extensions; |
|||
|
|||
private bool isPreviewFrame; // Non-serialized
|
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the frame encoding method (e.g., Modular or VarDCT).
|
|||
/// </summary>
|
|||
public JxlFrameEncoding Encoding |
|||
{ |
|||
get => this.encoding; |
|||
set => this.encoding = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the type of frame (e.g., RegularFrame).
|
|||
/// </summary>
|
|||
public JxlFrameType FrameType |
|||
{ |
|||
get => this.frameType; |
|||
set => this.frameType = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the frame flags.
|
|||
/// </summary>
|
|||
public ulong Flags |
|||
{ |
|||
get => this.flags; |
|||
set => this.flags = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the color transform used (e.g., XYB).
|
|||
/// </summary>
|
|||
public JxlColorTransform ColorTransform |
|||
{ |
|||
get => this.colorTransform; |
|||
set => this.colorTransform = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the chroma subsampling information.
|
|||
/// </summary>
|
|||
public JxlYCbCrChromaSubsampling? ChromaSubsampling |
|||
{ |
|||
get => this.chromaSubsampling; |
|||
set => this.chromaSubsampling = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the group size shift value.
|
|||
/// </summary>
|
|||
public uint GroupSizeShift |
|||
{ |
|||
get => this.groupSizeShift; |
|||
set => this.groupSizeShift = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the X quantization matrix scale.
|
|||
/// </summary>
|
|||
public uint XQmScale |
|||
{ |
|||
get => this.xQmScale; |
|||
set => this.xQmScale = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the B quantization matrix scale.
|
|||
/// </summary>
|
|||
public uint BQmScale |
|||
{ |
|||
get => this.bQmScale; |
|||
set => this.bQmScale = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the frame name.
|
|||
/// </summary>
|
|||
public string? Name |
|||
{ |
|||
get => this.name; |
|||
set => this.name = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the frame has a custom size or origin.
|
|||
/// </summary>
|
|||
public bool CustomSizeOrOrigin |
|||
{ |
|||
get => this.customSizeOrOrigin; |
|||
set => this.customSizeOrOrigin = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the frame size.
|
|||
/// </summary>
|
|||
public Size FrameSize |
|||
{ |
|||
get => this.frameSize; |
|||
set => this.frameSize = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the upsampling factor.
|
|||
/// </summary>
|
|||
public uint Upsampling |
|||
{ |
|||
get => this.upsampling; |
|||
set => this.upsampling = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the upsampling factors for extra channels.
|
|||
/// </summary>
|
|||
public List<uint> ExtraChannelUpsampling |
|||
{ |
|||
get => this.extraChannelUpsampling; |
|||
set => this.extraChannelUpsampling = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the frame origin point.
|
|||
/// </summary>
|
|||
public Point FrameOrigin |
|||
{ |
|||
get => this.frameOrigin; |
|||
set => this.frameOrigin = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the blending information for the frame.
|
|||
/// </summary>
|
|||
public JxlBlendingInfo? BlendingInfo |
|||
{ |
|||
get => this.blendingInfo; |
|||
set => this.blendingInfo = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the blending information for extra channels.
|
|||
/// </summary>
|
|||
public List<JxlBlendingInfo> ExtraChannelBlendingInfo |
|||
{ |
|||
get => this.extraChannelBlendingInfo; |
|||
set => this.extraChannelBlendingInfo = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the associated animation frame, if any.
|
|||
/// </summary>
|
|||
public JxlAnimationFrame? AnimationFrame => this.animationFrame; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether this is the last frame.
|
|||
/// </summary>
|
|||
public bool IsLast |
|||
{ |
|||
get => this.isLast; |
|||
set => this.isLast = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the reference frame index to save.
|
|||
/// </summary>
|
|||
public uint SaveAsReference |
|||
{ |
|||
get => this.saveAsReference; |
|||
set => this.saveAsReference = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to save before color transform.
|
|||
/// </summary>
|
|||
public bool SaveBeforeColorTransform |
|||
{ |
|||
get => this.saveBeforeColorTransform; |
|||
set => this.saveBeforeColorTransform = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the DC level of the frame.
|
|||
/// </summary>
|
|||
public uint DcLevel |
|||
{ |
|||
get => this.dcLevel; |
|||
set => this.dcLevel = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the codec metadata.
|
|||
/// </summary>
|
|||
public JxlCodecMetadata? Metadata |
|||
{ |
|||
get => this.metadata; |
|||
set => this.metadata = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the loop filter applied to the frame.
|
|||
/// </summary>
|
|||
public JxlLoopFilter? LoopFilter |
|||
{ |
|||
get => this.loopFilter; |
|||
set => this.loopFilter = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether this is a preview frame. Non-serialized.
|
|||
/// </summary>
|
|||
public bool IsPreviewFrame |
|||
{ |
|||
get => this.isPreviewFrame; |
|||
set => this.isPreviewFrame = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of extensions.
|
|||
/// </summary>
|
|||
public ulong Extensions |
|||
{ |
|||
get => this.extensions; |
|||
set => this.extensions = value; |
|||
} |
|||
|
|||
public int DefaultXSize |
|||
{ |
|||
get |
|||
{ |
|||
if (this.metadata == null) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
if (this.isPreviewFrame) |
|||
{ |
|||
return this.metadata.ImageMetadata?.PreviewSize?.XSize ?? 0; |
|||
} |
|||
|
|||
return this.metadata.XSize; |
|||
} |
|||
} |
|||
|
|||
public int DefaultYSize |
|||
{ |
|||
get |
|||
{ |
|||
if (this.metadata == null) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
if (this.isPreviewFrame) |
|||
{ |
|||
return this.metadata.ImageMetadata?.PreviewSize?.YSize ?? 0; |
|||
} |
|||
|
|||
return this.metadata.YSize; |
|||
} |
|||
} |
|||
|
|||
public JxlFrameDimensions FrameDimensions |
|||
{ |
|||
get |
|||
{ |
|||
int xsize = this.DefaultXSize; |
|||
int ysize = this.DefaultYSize; |
|||
|
|||
xsize = this.frameSize.Width != 0 ? this.frameSize.Width : xsize; |
|||
ysize = this.frameSize.Height != 0 ? this.frameSize.Height : ysize; |
|||
|
|||
if (this.dcLevel != 0) |
|||
{ |
|||
xsize = JxlMath.DivCeil(xsize, 1 << (3 * (int)this.dcLevel)); |
|||
ysize = JxlMath.DivCeil(ysize, 1 << (3 * (int)this.dcLevel)); |
|||
} |
|||
|
|||
JxlFrameDimensions frameDim = new( |
|||
xsize, |
|||
ysize, |
|||
(int)this.groupSizeShift, |
|||
this.chromaSubsampling?.MaxHShift ?? 0, |
|||
this.chromaSubsampling?.MaxVShift ?? 0, |
|||
this.encoding == JxlFrameEncoding.Modular, |
|||
(int)this.upsampling); |
|||
|
|||
return frameDim; |
|||
} |
|||
} |
|||
|
|||
public bool NeedsColorTransform => !this.saveBeforeColorTransform || |
|||
this.frameType == JxlFrameType.RegularFrame || |
|||
this.frameType == JxlFrameType.SkipProgressive; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this frame is supposed to be saved for future usage by other frames.
|
|||
/// </summary>
|
|||
public bool CanBeReferenced => // DC frames cannot be referenced. The last frame cannot be referenced.
|
|||
// A duration 0 frame makes little sense if it is not referenced.
|
|||
// A non-duration 0 frame may or may not be referenced.
|
|||
!this.isLast && |
|||
this.frameType != JxlFrameType.DcFrame && |
|||
(this.animationFrame?.Duration == 0 || this.saveAsReference != 0); |
|||
|
|||
private void UpdateFlag(bool condition, ulong flag) |
|||
{ |
|||
if (condition) |
|||
{ |
|||
this.flags |= flag; |
|||
} |
|||
else |
|||
{ |
|||
this.flags &= ~flag; |
|||
} |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) |
|||
{ |
|||
bool allDefault = false; |
|||
if (visitor.AllDefault(this, ref allDefault)) |
|||
{ |
|||
visitor.SetDefault(this); |
|||
return true; |
|||
} |
|||
|
|||
if (!VisitFrameType(visitor, JxlFrameType.RegularFrame, ref this.frameType)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (visitor.IsReading && this.isPreviewFrame && this.frameType != JxlFrameType.RegularFrame) |
|||
{ |
|||
throw new InvalidOperationException("Only regular frame could be a preview"); |
|||
} |
|||
|
|||
// FrameEncoding
|
|||
bool isModular = this.encoding == JxlFrameEncoding.Modular; |
|||
if (!visitor.Boolean(false, ref isModular)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.encoding = isModular |
|||
? JxlFrameEncoding.Modular |
|||
: JxlFrameEncoding.VarDct; |
|||
|
|||
// Flags
|
|||
if (!visitor.U64(0, ref this.flags)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
// Color transform
|
|||
bool xybEncoded = this.metadata?.ImageMetadata?.XybEncoded == true; |
|||
if (xybEncoded) |
|||
{ |
|||
this.colorTransform = JxlColorTransform.Xyb; |
|||
} |
|||
else |
|||
{ |
|||
bool alternate = this.colorTransform == JxlColorTransform.YCbCr; |
|||
if (!visitor.Boolean(false, ref alternate)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.colorTransform = alternate |
|||
? JxlColorTransform.YCbCr |
|||
: JxlColorTransform.None; |
|||
} |
|||
|
|||
// Chroma subsampling
|
|||
if (visitor.Conditional(this.colorTransform == JxlColorTransform.YCbCr && |
|||
((this.flags & (ulong)JxlFrameHeaderFlags.Dc) == 0))) |
|||
{ |
|||
if (!visitor.VisitNested(this.chromaSubsampling!)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
int numExtraChannels = this.metadata?.ImageMetadata?.ExtraChannelCount ?? 0; |
|||
|
|||
// Upsampling
|
|||
if (visitor.Conditional((this.flags & (ulong)JxlFrameHeaderFlags.Dc) == 0)) |
|||
{ |
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(1), |
|||
JxlFieldExpressions.Value(2), |
|||
JxlFieldExpressions.Value(4), |
|||
JxlFieldExpressions.Value(8), |
|||
1, |
|||
ref this.upsampling)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (this.metadata != null && visitor.Conditional(numExtraChannels != 0)) |
|||
{ |
|||
List<JxlExtraChannelInfo> extraChannels = this.metadata!.ImageMetadata?.ExtraChannels ?? []; |
|||
this.extraChannelUpsampling = new List<uint>(extraChannels.Count); |
|||
|
|||
for (int i = 0; i < extraChannels.Count; i++) |
|||
{ |
|||
uint dimShift = (uint)extraChannels[i].DimensionShift; |
|||
uint ecUpsampling = 1; |
|||
ecUpsampling >>= (int)dimShift; |
|||
|
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(1), |
|||
JxlFieldExpressions.Value(2), |
|||
JxlFieldExpressions.Value(4), |
|||
JxlFieldExpressions.Value(8), |
|||
1, |
|||
ref ecUpsampling)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
ecUpsampling <<= (int)dimShift; |
|||
|
|||
if (ecUpsampling < this.upsampling) |
|||
{ |
|||
throw new InvalidOperationException("EC upsampling < color upsampling, invalid"); |
|||
} |
|||
|
|||
if (ecUpsampling > 8) |
|||
{ |
|||
throw new InvalidOperationException("EC upsampling too large"); |
|||
} |
|||
|
|||
this.extraChannelUpsampling.Add(ecUpsampling); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.extraChannelUpsampling.Clear(); |
|||
} |
|||
} |
|||
|
|||
// Modular / VarDCT specifics
|
|||
if (visitor.Conditional(this.encoding == JxlFrameEncoding.Modular)) |
|||
{ |
|||
if (!visitor.Bits(2, 1, ref this.groupSizeShift)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
if (visitor.Conditional(this.encoding == JxlFrameEncoding.VarDct && |
|||
this.colorTransform == JxlColorTransform.Xyb)) |
|||
{ |
|||
if (!visitor.Bits(3, 3, ref this.xQmScale)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!visitor.Bits(3, 2, ref this.bQmScale)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.xQmScale = this.bQmScale = 2; |
|||
} |
|||
|
|||
// Passes
|
|||
if (visitor.Conditional(this.frameType != JxlFrameType.ReferenceOnly)) |
|||
{ |
|||
if (!visitor.VisitNested(this.passes)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
// DC frame
|
|||
if (visitor.Conditional(this.frameType == JxlFrameType.DcFrame)) |
|||
{ |
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(1), |
|||
JxlFieldExpressions.Value(2), |
|||
JxlFieldExpressions.Value(3), |
|||
JxlFieldExpressions.Value(4), |
|||
1, |
|||
ref this.dcLevel)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.dcLevel = 0; |
|||
} |
|||
|
|||
// Custom size/origin
|
|||
bool isPartialFrame = false; |
|||
|
|||
if (visitor.Conditional(this.frameType != JxlFrameType.DcFrame)) |
|||
{ |
|||
if (!visitor.Boolean(false, ref this.customSizeOrOrigin)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (visitor.Conditional(this.customSizeOrOrigin)) |
|||
{ |
|||
JxlU32Enc enc = new( |
|||
JxlFieldExpressions.Bits(8), |
|||
JxlFieldExpressions.BitsOffset(11, 256), |
|||
JxlFieldExpressions.BitsOffset(14, 2304), |
|||
JxlFieldExpressions.BitsOffset(30, 18688)); |
|||
|
|||
if (visitor.Conditional(this.frameType is JxlFrameType.RegularFrame or JxlFrameType.SkipProgressive)) |
|||
{ |
|||
uint ux0 = JxlPackSigned.PackUnsigned(this.frameOrigin.X); |
|||
uint uy0 = JxlPackSigned.PackUnsigned(this.frameOrigin.Y); |
|||
|
|||
if (!visitor.U32(enc, 0, ref ux0)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!visitor.U32(enc, 0, ref uy0)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.frameOrigin = new Point(JxlPackSigned.UnpackSigned(ux0), JxlPackSigned.UnpackSigned(uy0)); |
|||
} |
|||
|
|||
uint frameSizeWidth = (uint)this.frameSize.Width; |
|||
uint frameSizeHeight = (uint)this.frameSize.Height; |
|||
|
|||
if (!visitor.U32(enc, 0, ref frameSizeWidth)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!visitor.U32(enc, 0, ref frameSizeHeight)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (this.customSizeOrOrigin && (this.frameSize.Width == 0 || this.frameSize.Height == 0)) |
|||
{ |
|||
throw new InvalidOperationException("Invalid crop dimensions for frame"); |
|||
} |
|||
|
|||
int imageXSize = this.DefaultXSize; |
|||
int imageYSize = this.DefaultYSize; |
|||
|
|||
if (this.frameType is JxlFrameType.RegularFrame or JxlFrameType.SkipProgressive) |
|||
{ |
|||
isPartialFrame |= this.frameOrigin.X > 0; |
|||
isPartialFrame |= this.frameOrigin.Y > 0; |
|||
isPartialFrame |= (this.frameSize.Width + this.frameOrigin.X) < imageXSize; |
|||
isPartialFrame |= (this.frameSize.Height + this.frameOrigin.Y) < imageYSize; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Blending, animation, last frame
|
|||
if (visitor.Conditional(this.frameType is JxlFrameType.RegularFrame or JxlFrameType.SkipProgressive)) |
|||
{ |
|||
this.blendingInfo!.ExtraChannelCount = numExtraChannels; |
|||
this.blendingInfo.IsPartialFrame = isPartialFrame; |
|||
|
|||
if (!visitor.VisitNested(this.blendingInfo)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
bool replaceAll = this.blendingInfo.BlendMode == JxlBlendMode.Replace; |
|||
|
|||
this.extraChannelBlendingInfo = new List<JxlBlendingInfo>(numExtraChannels); |
|||
for (int i = 0; i < numExtraChannels; i++) |
|||
{ |
|||
JxlBlendingInfo ecBlendingInfo = new() |
|||
{ |
|||
IsPartialFrame = isPartialFrame, |
|||
ExtraChannelCount = numExtraChannels |
|||
}; |
|||
|
|||
if (!visitor.VisitNested(ecBlendingInfo)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.extraChannelBlendingInfo.Add(ecBlendingInfo); |
|||
replaceAll &= ecBlendingInfo.BlendMode == JxlBlendMode.Replace; |
|||
} |
|||
|
|||
if (visitor.IsReading && this.isPreviewFrame) |
|||
{ |
|||
if (!replaceAll || this.customSizeOrOrigin) |
|||
{ |
|||
throw new InvalidOperationException("Preview is not compatible with blending"); |
|||
} |
|||
} |
|||
|
|||
if (visitor.Conditional(this.metadata?.ImageMetadata?.HaveAnimation == true)) |
|||
{ |
|||
this.animationFrame!.CodecMetadata = this.metadata; |
|||
|
|||
if (!visitor.VisitNested(this.animationFrame!)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
if (!visitor.Boolean(true, ref this.isLast)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.isLast = false; |
|||
} |
|||
|
|||
// SaveAsReference
|
|||
if (visitor.Conditional(this.frameType != JxlFrameType.DcFrame && !this.isLast)) |
|||
{ |
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(0), |
|||
JxlFieldExpressions.Value(1), |
|||
JxlFieldExpressions.Value(2), |
|||
JxlFieldExpressions.Value(3), |
|||
0, |
|||
ref this.saveAsReference)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
// SaveBeforeColorTransform logic
|
|||
if (this.frameType != JxlFrameType.DcFrame) |
|||
{ |
|||
if (visitor.Conditional( |
|||
this.CanBeReferenced && |
|||
this.blendingInfo?.BlendMode == JxlBlendMode.Replace && |
|||
!isPartialFrame && |
|||
(this.frameType == JxlFrameType.RegularFrame || |
|||
this.frameType == JxlFrameType.SkipProgressive))) |
|||
{ |
|||
if (!visitor.Boolean(false, ref this.saveBeforeColorTransform)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
else if (visitor.Conditional(this.frameType == JxlFrameType.ReferenceOnly)) |
|||
{ |
|||
if (!visitor.Boolean(true, ref this.saveBeforeColorTransform)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
int xsize = this.customSizeOrOrigin |
|||
? this.frameSize.Width |
|||
: this.metadata!.XSize; |
|||
|
|||
int ysize = this.customSizeOrOrigin |
|||
? this.frameSize.Height |
|||
: this.metadata!.YSize; |
|||
|
|||
if (!this.saveBeforeColorTransform && |
|||
(xsize < this.metadata!.XSize || |
|||
ysize < this.metadata!.YSize || |
|||
this.frameOrigin.X != 0 || |
|||
this.frameOrigin.Y != 0)) |
|||
{ |
|||
throw new InvalidOperationException("Non-patch reference frame with invalid crop"); |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.saveBeforeColorTransform = true; |
|||
} |
|||
|
|||
if (!VisitNameString(visitor, ref this.name)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.loopFilter!.IsModular = isModular; |
|||
if (!visitor.VisitNested(this.loopFilter!)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!visitor.BeginExtensions(ref this.extensions)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return visitor.EndExtensions(); |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
|
|||
/// <summary>
|
|||
/// Optional steps for postprocessing. These flags are the
|
|||
/// source of truth. Override must set/clear them rather than
|
|||
/// change their meaning. Values chosen such that typical flags
|
|||
/// are 0, encoded in only two bits.
|
|||
/// </summary>
|
|||
[Flags] |
|||
internal enum JxlFrameHeaderFlags : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Noise is injected into decoded output.
|
|||
/// </summary>
|
|||
Noise = 1, |
|||
|
|||
/// <summary>
|
|||
/// Overlay patches.
|
|||
/// </summary>
|
|||
Patches = 2, |
|||
|
|||
/// <summary>
|
|||
/// Overlay splines.
|
|||
/// </summary>
|
|||
Splines = 16, |
|||
|
|||
/// <summary>
|
|||
/// Implies skip adaptive DC smoothing.
|
|||
/// </summary>
|
|||
Dc = 32, |
|||
|
|||
SkipAdaptiveDcSmoothing = 128, |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
|
|||
/// <summary>
|
|||
/// Defines the type of a JPEG XL frame.
|
|||
/// </summary>
|
|||
internal enum JxlFrameType : byte |
|||
{ |
|||
/// <summary>
|
|||
/// A regular frame. It might be a crop, and it will be blended
|
|||
/// on a previous frame (if any) and likely displayed or blended in
|
|||
/// future frames.
|
|||
/// </summary>
|
|||
RegularFrame, |
|||
|
|||
/// <summary>
|
|||
/// A DC frame. It is downsampled and only used as the DC
|
|||
/// of a future and, possibly, preview frame. This cannot be cropped,
|
|||
/// blended, or referenced by patches or blending modes. Frames using
|
|||
/// DC cannot have non-default sizes.
|
|||
/// </summary>
|
|||
DcFrame, |
|||
|
|||
/// <summary>
|
|||
/// A PatchesSource frame. Can only be used as source frame for
|
|||
/// taking patches. It can be cropped but can't have a non-(0, 0) x0/y0.
|
|||
/// </summary>
|
|||
ReferenceOnly = 2, |
|||
|
|||
/// <summary>
|
|||
/// Same as regular frame but not used for progressive rendering.
|
|||
/// Implies no early display of DC.
|
|||
/// </summary>
|
|||
SkipProgressive, |
|||
} |
|||
@ -0,0 +1,220 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
|
|||
/// <summary>
|
|||
/// Used for decoding to lower resolutions.
|
|||
/// </summary>
|
|||
internal sealed class JxlPasses : IJxlFields |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the maximum amount of passes, which is 11.
|
|||
/// </summary>
|
|||
private const int MaxPasses = 11; |
|||
|
|||
private uint numPasses; |
|||
private uint numDownsample; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of passes.
|
|||
/// </summary>
|
|||
public uint NumberOfPasses |
|||
{ |
|||
get => this.numPasses; |
|||
set => this.numPasses = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of downsamples.
|
|||
/// </summary>
|
|||
public uint NumberOfDownsamples |
|||
{ |
|||
get => this.numDownsample; |
|||
set => this.numDownsample = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the downsample values.
|
|||
/// </summary>
|
|||
public uint[] Downsample { get; } = new uint[MaxPasses]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the last pass values.
|
|||
/// </summary>
|
|||
public uint[] LastPass { get; } = new uint[MaxPasses]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the shift values.
|
|||
/// </summary>
|
|||
public uint[] Shift { get; } = new uint[MaxPasses]; |
|||
|
|||
public void GetDownsamplingBracket(int pass, out int minShift, out int maxShift) |
|||
{ |
|||
maxShift = 2; |
|||
minShift = 3; |
|||
|
|||
for (int i = 0; ; i++) |
|||
{ |
|||
for (int j = 0; j < this.numDownsample; ++j) |
|||
{ |
|||
if (i == this.LastPass[j]) |
|||
{ |
|||
uint ds = this.Downsample[j]; |
|||
|
|||
if (ds == 8) |
|||
{ |
|||
minShift = 3; |
|||
} |
|||
|
|||
if (ds == 4) |
|||
{ |
|||
minShift = 2; |
|||
} |
|||
|
|||
if (ds == 2) |
|||
{ |
|||
minShift = 1; |
|||
} |
|||
|
|||
if (ds == 1) |
|||
{ |
|||
minShift = 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (i == this.numPasses - 1) |
|||
{ |
|||
minShift = 0; |
|||
} |
|||
|
|||
if (i == pass) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
maxShift = minShift - 1; |
|||
} |
|||
} |
|||
|
|||
public uint GetDownsamplingTargetForCompletedPasses(int num) |
|||
{ |
|||
if (num >= this.numPasses) |
|||
{ |
|||
return 1; |
|||
} |
|||
|
|||
uint result = 0; |
|||
|
|||
for (int i = 0; i < this.numDownsample; i++) |
|||
{ |
|||
if (num > this.LastPass[i]) |
|||
{ |
|||
result = Math.Min(result, this.Downsample[i]); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) |
|||
{ |
|||
if (visitor.U32( |
|||
JxlFieldExpressions.Value(1), |
|||
JxlFieldExpressions.Value(2), |
|||
JxlFieldExpressions.Value(2), |
|||
JxlFieldExpressions.BitsOffset(1, 3), |
|||
0, |
|||
ref this.numPasses)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (this.numPasses > MaxPasses) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (visitor.Conditional(this.numPasses != 1)) |
|||
{ |
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(0), |
|||
JxlFieldExpressions.Value(1), |
|||
JxlFieldExpressions.Value(2), |
|||
JxlFieldExpressions.BitsOffset(1, 3), |
|||
0, |
|||
ref this.numDownsample)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (this.numDownsample > 4) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (this.numDownsample > this.numPasses) |
|||
{ |
|||
throw new InvalidOperationException("Number of downsaples is greater than number of passes"); |
|||
} |
|||
|
|||
for (int i = 0; i < this.numPasses - 1; i++) |
|||
{ |
|||
if (!visitor.Bits(2, 0u, ref this.Shift[i])) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
this.Shift[this.numPasses - 1] = 0; |
|||
|
|||
for (int i = 0; i < this.numDownsample; i++) |
|||
{ |
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(1), |
|||
JxlFieldExpressions.Value(2), |
|||
JxlFieldExpressions.Value(4), |
|||
JxlFieldExpressions.Value(8), |
|||
1, |
|||
ref this.Downsample[i])) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (i > 0 && this.Downsample[i] >= this.Downsample[i - 1]) |
|||
{ |
|||
throw new InvalidOperationException("Downsample sequence should decrease"); |
|||
} |
|||
} |
|||
|
|||
for (int i = 0; i < this.numDownsample; i++) |
|||
{ |
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(0), |
|||
JxlFieldExpressions.Value(1), |
|||
JxlFieldExpressions.Value(2), |
|||
JxlFieldExpressions.Value(3), |
|||
0, |
|||
ref this.LastPass[i])) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (i > 0 && this.LastPass[i] <= this.LastPass[i - 1]) |
|||
{ |
|||
throw new InvalidOperationException("Last pass sequence should increase"); |
|||
} |
|||
|
|||
if (this.LastPass[i] >= this.numPasses) |
|||
{ |
|||
throw new InvalidOperationException("Last pass is greater than number of passes"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,147 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; |
|||
|
|||
/// <summary>
|
|||
/// Gets the Y'Cb'Cr chroma subsampling information as part
|
|||
/// of the JPEG XL Frame Header.
|
|||
/// </summary>
|
|||
internal sealed class JxlYCbCrChromaSubsampling : IJxlFields |
|||
{ |
|||
private readonly int[] channelMode = new int[3]; |
|||
|
|||
private static ReadOnlySpan<byte> HShiftData => [0, 1, 1, 0]; |
|||
|
|||
private static ReadOnlySpan<byte> VShiftData => [0, 1, 0, 1]; |
|||
|
|||
public byte MaxHShift { get; private set; } |
|||
|
|||
public byte MaxVShift { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this 4:4:4 chroma subsampling.
|
|||
/// </summary>
|
|||
public bool Is444 => |
|||
this.HShift(0) == 0 && this.VShift(0) == 0 && // Cb
|
|||
this.HShift(2) == 0 && this.VShift(2) == 0 && // Cr
|
|||
this.HShift(1) == 0 && this.VShift(1) == 0; // Y;
|
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this 4:2:0 chroma subsampling.
|
|||
/// </summary>
|
|||
public bool Is420 => |
|||
this.HShift(0) == 1 && this.VShift(0) == 1 && // Cb
|
|||
this.HShift(2) == 1 && this.VShift(2) == 1 && // Cr
|
|||
this.HShift(1) == 0 && this.VShift(1) == 0; // Y
|
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this 4:2:2 chroma subsampling.
|
|||
/// </summary>
|
|||
public bool Is422 => |
|||
this.HShift(0) == 1 && this.VShift(0) == 0 && // Cb
|
|||
this.HShift(2) == 1 && this.VShift(2) == 0 && // Cr
|
|||
this.HShift(1) == 0 && this.VShift(1) == 0; // Y
|
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this 4:4:0 chroma subsampling.
|
|||
/// </summary>
|
|||
public bool Is440 => |
|||
this.HShift(0) == 0 && this.VShift(0) == 1 && // Cb
|
|||
this.HShift(2) == 0 && this.VShift(2) == 1 && // Cr
|
|||
this.HShift(1) == 0 && this.VShift(1) == 0; // Y
|
|||
|
|||
public byte RawHShift(int c) => HShiftData[this.channelMode[c]]; |
|||
|
|||
public byte RawVShift(int c) => VShiftData[this.channelMode[c]]; |
|||
|
|||
public byte HShift(int c) => (byte)(this.MaxHShift - HShiftData[this.channelMode[c]]); |
|||
|
|||
public byte VShift(int c) => (byte)(this.MaxVShift - VShiftData[this.channelMode[c]]); |
|||
|
|||
private void Recompute() |
|||
{ |
|||
this.MaxHShift = 0; |
|||
this.MaxVShift = 0; |
|||
|
|||
for (int i = 0; i < 3; i++) |
|||
{ |
|||
int ch = this.channelMode[i]; |
|||
|
|||
this.MaxHShift = Math.Max(this.MaxHShift, HShiftData[ch]); |
|||
this.MaxVShift = Math.Max(this.MaxVShift, VShiftData[ch]); |
|||
} |
|||
} |
|||
|
|||
public bool Set(ReadOnlySpan<byte> hsample, ReadOnlySpan<byte> vsample) |
|||
{ |
|||
for (int c = 0; c < 3; c++) |
|||
{ |
|||
int cjpeg = c < 2 ? (c ^ 1) : c; |
|||
int i = 0; |
|||
|
|||
for (; i < 4; i++) |
|||
{ |
|||
if (1 << HShiftData[i] == hsample[cjpeg] && 1 << VShiftData[i] == vsample[cjpeg]) |
|||
{ |
|||
this.channelMode[c] = i; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (i == 4) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
this.Recompute(); |
|||
return true; |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
if (this.Is444) |
|||
{ |
|||
return "4:4:4"; |
|||
} |
|||
else if (this.Is420) |
|||
{ |
|||
return "4:2:0"; |
|||
} |
|||
else if (this.Is422) |
|||
{ |
|||
return "4:2:2"; |
|||
} |
|||
else if (this.Is440) |
|||
{ |
|||
return "4:4:0"; |
|||
} |
|||
else |
|||
{ |
|||
return $"[Custom] {this.channelMode[0]}:{this.channelMode[1]}:{this.channelMode[2]}"; |
|||
} |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) |
|||
{ |
|||
for (int i = 0; i < 3; i++) |
|||
{ |
|||
int channel = this.channelMode[i]; |
|||
|
|||
uint unsignedChannel = (uint)channel; |
|||
bool wroteSuccessfully = visitor.Bits(2, 0, ref unsignedChannel); |
|||
|
|||
if (!wroteSuccessfully) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.channelMode[i] = (int)unsignedChannel; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg.Data; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the kind of APP marker in a JPEG file.
|
|||
/// </summary>
|
|||
internal enum JpegAppMarkerType : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Unknown APP marker
|
|||
/// </summary>
|
|||
Unknown, |
|||
|
|||
/// <summary>
|
|||
/// Contains ICC profile metadata
|
|||
/// </summary>
|
|||
Icc, |
|||
|
|||
/// <summary>
|
|||
/// Contains EXIF profile metadata
|
|||
/// </summary>
|
|||
Exif, |
|||
|
|||
/// <summary>
|
|||
/// Contains XMP profile metadata
|
|||
/// </summary>
|
|||
Xmp |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg.Data; |
|||
|
|||
// We may need to get a ref to fields, so don't
|
|||
// make these properties.
|
|||
#pragma warning disable SA1401 // Fields should be private
|
|||
|
|||
/// <summary>
|
|||
/// Represents one component of a jpeg file.
|
|||
/// </summary>
|
|||
internal sealed class JpegComponent |
|||
{ |
|||
/// <summary>
|
|||
/// One-byte id of the component
|
|||
/// </summary>
|
|||
public int Id; |
|||
|
|||
/// <summary>
|
|||
/// In interleaved mode, each minimal coded unit (MCU)
|
|||
/// has horizontal x vertical sample factor DCT blocks
|
|||
/// from this component. This is the horizontal factor.
|
|||
/// </summary>
|
|||
public int HorizontalSampleFactor = 1; |
|||
|
|||
/// <summary>
|
|||
/// In interleaved mode, each minimal coded unit (MCU)
|
|||
/// has horizontal x vertical sample factor DCT blocks
|
|||
/// from this component. This is the vertical factor.
|
|||
/// </summary>
|
|||
public int VerticalSampleFactor = 1; |
|||
|
|||
/// <summary>
|
|||
/// Index of quantization table used for this component.
|
|||
/// </summary>
|
|||
public int QuantIndex; |
|||
|
|||
/// <summary>
|
|||
/// Width measured in 8x8 blocks
|
|||
/// </summary>
|
|||
public int WidthInBlocks; |
|||
|
|||
/// <summary>
|
|||
/// Width measured in 8x8 blocks
|
|||
/// </summary>
|
|||
public int HeightInBlocks; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets DCT coefficients.
|
|||
/// </summary>
|
|||
public List<int> Coefficients { get; set; } = []; |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg.Data; |
|||
|
|||
/// <summary>
|
|||
/// Huffman table indexes used for one component of one scan.
|
|||
/// </summary>
|
|||
// We may need to get a ref to fields, so don't
|
|||
// make these properties.
|
|||
internal struct JpegComponentScanInfo |
|||
{ |
|||
public int ComponentIndex; |
|||
public int DcTableIndex; |
|||
public int AcTableIndex; |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg.Data; |
|||
|
|||
internal enum JpegComponentType : byte |
|||
{ |
|||
Gray, |
|||
|
|||
YCbCr, |
|||
|
|||
Rgb, |
|||
|
|||
Custom |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,105 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg.Data; |
|||
|
|||
/// <summary>
|
|||
/// Constants used in parsed JPEG data.
|
|||
/// </summary>
|
|||
internal static class JpegDataConstants |
|||
{ |
|||
/// <summary>
|
|||
/// Maximum number of components.
|
|||
/// </summary>
|
|||
public const int MaxComponents = 4; |
|||
|
|||
/// <summary>
|
|||
/// Maximum number of quantizer tables.
|
|||
/// </summary>
|
|||
public const int MaximumQuantizationTables = 4; |
|||
|
|||
/// <summary>
|
|||
/// Maximum number of Huffman code tables.
|
|||
/// </summary>
|
|||
public const int MaxHuffmanTables = 4; |
|||
|
|||
/// <summary>
|
|||
/// Maximum number of bits for a Huffman code.
|
|||
/// </summary>
|
|||
public const int JpegHuffmanMaxBitLength = 16; |
|||
|
|||
/// <summary>
|
|||
/// Alphabet size for Huffman tables used in the JPEG format.
|
|||
/// </summary>
|
|||
public const int JpegHuffmanAlphabetSize = 256; |
|||
|
|||
/// <summary>
|
|||
/// Alphabet size for Huffman tables used in the JPEG format.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This is specific to the DC coefficients of the Discrete
|
|||
/// Cosine Transform.
|
|||
/// </remarks>
|
|||
public const int JpegDcAlphabetSize = 12; |
|||
|
|||
/// <summary>
|
|||
/// Maximum number of DHT "Define Huffman Tables" markers.
|
|||
/// </summary>
|
|||
public const int MaxDhtMarkers = 512; |
|||
|
|||
/// <summary>
|
|||
/// Largest value for width OR height.
|
|||
/// </summary>
|
|||
public const int MaxDimPixels = 65535; |
|||
|
|||
/// <summary>
|
|||
/// Marker that specifies APP1.
|
|||
/// </summary>
|
|||
public const int App1 = 0xE1; |
|||
|
|||
/// <summary>
|
|||
/// Marker that specifies APP2.
|
|||
/// </summary>
|
|||
public const int App2 = 0xE2; |
|||
|
|||
/// <summary>
|
|||
/// Gets the tag bytes specifying the ICC profile.
|
|||
/// </summary>
|
|||
public static ReadOnlySpan<byte> IccProfileTag => "ICC_PROFILE\0"u8; |
|||
|
|||
/// <summary>
|
|||
/// Gets the tag bytes specifying the EXIF profile.
|
|||
/// </summary>
|
|||
public static ReadOnlySpan<byte> ExifTag => "Exif\0\0"u8; |
|||
|
|||
/// <summary>
|
|||
/// Gets the tag bytes specifying the XMP profile.
|
|||
/// </summary>
|
|||
public static ReadOnlySpan<byte> XmpTag => "http://ns.adobe.com/xap/1.0/\0"u8; |
|||
|
|||
public static ReadOnlySpan<int> JpegNaturalOrder => |
|||
[ |
|||
0, 1, 8, 16, 9, 2, 3, 10, |
|||
17, 24, 32, 25, 18, 11, 4, 5, |
|||
12, 19, 26, 33, 40, 48, 41, 34, |
|||
27, 20, 13, 6, 7, 14, 21, 28, |
|||
35, 42, 49, 56, 57, 50, 43, 36, |
|||
29, 22, 15, 23, 30, 37, 44, 51, |
|||
58, 59, 52, 45, 38, 31, 39, 46, |
|||
53, 60, 61, 54, 47, 55, 62, 63, |
|||
63, 63, 63, 63, 63, 63, 63, 63, |
|||
63, 63, 63, 63, 63, 63, 63, 63 |
|||
]; |
|||
|
|||
public static ReadOnlySpan<int> JpegZigZagOrder => |
|||
[ |
|||
0, 1, 5, 6, 14, 15, 27, 28, |
|||
2, 4, 7, 13, 16, 26, 29, 42, |
|||
3, 8, 12, 17, 25, 30, 41, 43, |
|||
9, 11, 18, 24, 31, 40, 44, 53, |
|||
10, 19, 23, 32, 39, 45, 52, 54, |
|||
20, 22, 33, 38, 46, 51, 55, 60, |
|||
21, 34, 37, 47, 50, 56, 59, 61, |
|||
35, 36, 48, 49, 57, 58, 62, 63 |
|||
]; |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg.Data; |
|||
|
|||
internal struct JpegExtraZeroRunInfo |
|||
{ |
|||
public int BlockIndex; |
|||
|
|||
public int NumExtraZeroRuns; |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg.Data; |
|||
|
|||
// We may need to get a ref to fields, so don't
|
|||
// make these properties.
|
|||
internal struct JpegHuffmanCode() |
|||
{ |
|||
/// <summary>
|
|||
/// Bit length histogram
|
|||
/// </summary>
|
|||
public InlineArray17<int> Counts; |
|||
|
|||
/// <summary>
|
|||
/// Symbol values stored by increasing bit lengths.
|
|||
/// </summary>
|
|||
public InlineArray17<int> Values; |
|||
|
|||
/// <summary>
|
|||
/// The index of the code in the current set of Huffman codes.
|
|||
/// For AC component Huffman codes, 0x10 is added to the index.
|
|||
/// </summary>
|
|||
public int SlotId; |
|||
|
|||
/// <summary>
|
|||
/// True if the code is last within its marker segment.
|
|||
/// </summary>
|
|||
public bool IsLast = true; |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg.Data; |
|||
|
|||
internal struct JpegInfo |
|||
{ |
|||
public int NumberOfAppMarkers { get; set; } |
|||
|
|||
public int NumberOfComMarkers { get; set; } |
|||
|
|||
public int NumberOfScans { get; set; } |
|||
|
|||
public int NumberOfIntermarkers { get; set; } |
|||
|
|||
public bool HasDri { get; set; } |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg.Data; |
|||
|
|||
/// <summary>
|
|||
/// Representation of quantization values for an 8x8 pixel block.
|
|||
/// </summary>
|
|||
// We may need to get a ref to fields, so don't
|
|||
// make these properties.
|
|||
internal struct JpegQuantizationTable() |
|||
{ |
|||
/// <summary>
|
|||
/// Quantization values
|
|||
/// </summary>
|
|||
public InlineArray64<int> Values; |
|||
|
|||
public int Precision; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the index of the quantization table
|
|||
/// as it was parsed from the input JPEG.
|
|||
/// </summary>
|
|||
public int Index; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether this table
|
|||
/// is the last one within its marker segment.
|
|||
/// </summary>
|
|||
public bool IsLast = true; |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg.Data; |
|||
|
|||
// We may need to get a ref to fields, so don't
|
|||
// make these properties.
|
|||
#pragma warning disable SA1401 // Fields should be private
|
|||
|
|||
internal sealed class JpegScanInfo |
|||
{ |
|||
// Variables copied from ITU-T T.81 spec
|
|||
|
|||
/// <summary>
|
|||
/// Start of spectral band in zigzag sequence
|
|||
/// </summary>
|
|||
public int Ss; |
|||
|
|||
/// <summary>
|
|||
/// End of spectral band in zigzag sequence
|
|||
/// </summary>
|
|||
public int Se; |
|||
|
|||
/// <summary>
|
|||
/// Successive approximation bit position. (High)
|
|||
/// </summary>
|
|||
public int Ah; |
|||
|
|||
/// <summary>
|
|||
/// Successive approximation bit position. (Low)
|
|||
/// </summary>
|
|||
public int Al; |
|||
|
|||
public int NumComponents; |
|||
|
|||
public InlineArray4<JpegComponentScanInfo> Components; |
|||
|
|||
public int LastNeededPass; |
|||
|
|||
public List<int> ResetPoints { get; set; } = []; |
|||
|
|||
public List<JpegExtraZeroRunInfo> ExtraZeroRuns { get; set; } = []; |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg; |
|||
|
|||
/// <summary>
|
|||
/// Decodes Huffman codes in a JPEG file for JPEG to JPEG XL encoder.
|
|||
/// </summary>
|
|||
internal static class JpegHuffmanDecoder |
|||
{ |
|||
private const int RootTableBits = 8; |
|||
private const int LookupSize = 8; |
|||
|
|||
private static int NextTableBitSize(Span<int> count, int length) |
|||
{ |
|||
int left = 1 << (length - RootTableBits); |
|||
while (length < MaxBitLength) |
|||
{ |
|||
left -= count[length]; |
|||
|
|||
if (left <= 0) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
length++; |
|||
left <<= 1; |
|||
} |
|||
|
|||
return length - RootTableBits; |
|||
} |
|||
|
|||
public struct HuffmanTableEntry() |
|||
{ |
|||
public byte Bits = 0; |
|||
public ushort Value = 0xFFFF; |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
# Jxl/Processing/Jpeg |
|||
This folder contains logic to: |
|||
|
|||
- Parse and represent JPEG data |
|||
- Write JPEG markers |
|||
- JPEG to JXL |
|||
- JXL to JPEG |
|||
|
|||
This does not contain a JPEG codec. |
|||
|
|||
Logic in this folder is used for JPEG<->JXL lossless compression. |
|||
@ -0,0 +1,184 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
/// <summary>
|
|||
/// Shared Huffman I/O utilities.
|
|||
/// </summary>
|
|||
internal static class JxlHuffman |
|||
{ |
|||
/// <summary>
|
|||
/// Returns Reverse(Reverse(Key, Len) + 1, Len). The
|
|||
/// Reverse(Key, Len) function performs bitwise reversal
|
|||
/// of the len least significant bits of the key value.
|
|||
/// </summary>
|
|||
public static uint GetNextKey(uint key, int len) |
|||
{ |
|||
uint step = 1u << (len - 1); |
|||
while ((key & step) != 0) |
|||
{ |
|||
step >>= 1; |
|||
} |
|||
|
|||
return (key & (step - 1)) + step; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Replicates <paramref name="code"/> into <paramref name="table"/> every <paramref name="step"/>
|
|||
/// times with the upper bound of <paramref name="end"/>.
|
|||
/// </summary>
|
|||
public static void ReplicateValue(Span<JxlHuffmanCode> table, int step, int end, JxlHuffmanCode code) |
|||
{ |
|||
do |
|||
{ |
|||
end -= step; |
|||
table[end] = code; |
|||
} |
|||
while (end > 0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the table width of the next 2nd level table.
|
|||
/// </summary>
|
|||
/// <param name="count">The histogram of bit lengths for remaining symbols</param>
|
|||
/// <param name="length">Code length of the next processed symbol</param>
|
|||
/// <param name="rootBits">Amount of bits for the root symbol</param>
|
|||
/// <returns>Table width for the 2nd level table.</returns>
|
|||
public static int NextTableBitSize(ReadOnlySpan<ushort> count, int length, int rootBits) |
|||
{ |
|||
uint left = 1u << (length - rootBits); |
|||
|
|||
while (length < JxlAnsConstants.PrefixMaxBits) |
|||
{ |
|||
if (left <= count[length]) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
left -= count[length]; |
|||
length++; |
|||
left <<= 1; |
|||
} |
|||
|
|||
return length - rootBits; |
|||
} |
|||
|
|||
public static uint BuildHuffmanTable( |
|||
Span<JxlHuffmanCode> rootTable, |
|||
int rootBits, |
|||
ReadOnlySpan<byte> codeLengths, |
|||
Span<ushort> count) |
|||
{ |
|||
if (codeLengths.Length > (1u << JxlAnsConstants.PrefixMaxBits)) |
|||
{ |
|||
return 0u; |
|||
} |
|||
|
|||
Span<ushort> offset = stackalloc ushort[JxlAnsConstants.PrefixMaxBits + 1]; |
|||
|
|||
Span<ushort> sortedStorage = stackalloc ushort[codeLengths.Length]; |
|||
|
|||
int maxLength = 1; |
|||
ushort sum = 0; |
|||
int len, symbol; |
|||
for (len = 1; len <= JxlAnsConstants.PrefixMaxBits; len++) |
|||
{ |
|||
offset[len] = sum; |
|||
|
|||
if (count[len] != 0) |
|||
{ |
|||
sum = (ushort)(sum + count[len]); |
|||
maxLength = len; |
|||
} |
|||
} |
|||
|
|||
for (symbol = 0; symbol < codeLengths.Length; symbol++) |
|||
{ |
|||
if (codeLengths[symbol] != 0) |
|||
{ |
|||
sortedStorage[offset[codeLengths[symbol]]++] = (ushort)symbol; |
|||
} |
|||
} |
|||
|
|||
Span<JxlHuffmanCode> table = rootTable; |
|||
int tableBits = rootBits; |
|||
uint tableSize = 1u << tableBits; |
|||
uint totalSize = tableSize; |
|||
|
|||
JxlHuffmanCode code = default; |
|||
|
|||
if (offset[JxlAnsConstants.PrefixMaxBits] == 1) |
|||
{ |
|||
code.Bits = 0; |
|||
code.Value = sortedStorage[0]; |
|||
|
|||
for (int i = 0; i < totalSize; i++) |
|||
{ |
|||
table[i] = code; |
|||
} |
|||
} |
|||
|
|||
if (tableBits > maxLength) |
|||
{ |
|||
tableBits = maxLength; |
|||
tableSize = 1u << tableBits; |
|||
} |
|||
|
|||
int key = 0; |
|||
code.Bits = 0; |
|||
int step = 2; |
|||
|
|||
do |
|||
{ |
|||
for (; count[code.Bits] != 0; --count[code.Bits]) |
|||
{ |
|||
code.Value = sortedStorage[symbol++]; |
|||
ReplicateValue(table[key..], step, (int)tableSize, code); |
|||
key = (int)GetNextKey((uint)key, code.Bits); |
|||
} |
|||
|
|||
step <<= 1; |
|||
} |
|||
while (++code.Bits <= tableBits); |
|||
|
|||
while (totalSize != tableSize) |
|||
{ |
|||
table[..(int)tableSize].CopyTo(table[(int)tableSize..]); |
|||
tableSize <<= 1; |
|||
} |
|||
|
|||
uint mask = totalSize - 1u; |
|||
int low = -1; |
|||
|
|||
uint tableOffset = 0; |
|||
for (step = 2; len <= maxLength; len++, step <<= 1) |
|||
{ |
|||
for (; count[len] != 0; --count[len]) |
|||
{ |
|||
if ((key & mask) != low) |
|||
{ |
|||
tableOffset += tableSize; |
|||
table = table[(int)tableSize..]; |
|||
tableBits = NextTableBitSize(count, len, rootBits); |
|||
tableSize = 1u << tableBits; |
|||
totalSize += tableSize; |
|||
low = key & (int)mask; |
|||
|
|||
rootTable[low].Bits = (byte)(tableBits + rootBits); |
|||
rootTable[low].Value = (ushort)(tableOffset - low); |
|||
} |
|||
|
|||
code.Bits = (byte)(len - rootBits); |
|||
code.Value = sortedStorage[symbol++]; |
|||
|
|||
ReplicateValue(table[(key >> rootBits)..], step, (int)tableSize, code); |
|||
key = (int)GetNextKey((uint)key, len); |
|||
} |
|||
} |
|||
|
|||
return totalSize; |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
/// <summary>
|
|||
/// A single Huffman code.
|
|||
/// </summary>
|
|||
internal struct JxlHuffmanCode(byte bits, ushort value) |
|||
{ |
|||
/// <summary>
|
|||
/// Number of bits for this symbol.
|
|||
/// </summary>
|
|||
public byte Bits = bits; |
|||
|
|||
/// <summary>
|
|||
/// Symbol value/offset.
|
|||
/// </summary>
|
|||
public ushort Value = value; |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO; |
|||
|
|||
/// <summary>
|
|||
/// A disposable writer for bytes in memory. It is highly similar to
|
|||
/// <see cref="MemoryStream"/>, but its buffer relies on
|
|||
/// <see cref="MemoryAllocator"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlMemoryWriter(MemoryAllocator allocator) : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The initial capacity in bytes.
|
|||
/// </summary>
|
|||
private const int InitialCapacity = 1024; |
|||
|
|||
/// <summary>
|
|||
/// Core buffer.
|
|||
/// </summary>
|
|||
private IMemoryOwner<byte> buffer = allocator.Allocate<byte>(InitialCapacity); |
|||
|
|||
/// <summary>
|
|||
/// Gets the length of the written data in bytes.
|
|||
/// </summary>
|
|||
public int Length { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the capacity of the buffer in bytes.
|
|||
/// </summary>
|
|||
public int Capacity => this.buffer.Memory.Length; |
|||
|
|||
/// <summary>
|
|||
/// Releases the underlying buffer.
|
|||
/// </summary>
|
|||
public void Dispose() => this.buffer.Dispose(); |
|||
|
|||
/// <summary>
|
|||
/// Writes the specified bytes into the writer.
|
|||
/// </summary>
|
|||
/// <param name="bytes">The bytes to write.</param>
|
|||
public void Write(ReadOnlySpan<byte> bytes) |
|||
{ |
|||
int requiredCapacity = checked(this.Length + bytes.Length); |
|||
this.EnsureCapacity(requiredCapacity); |
|||
|
|||
bytes.CopyTo(this.buffer.Memory.Span[this.Length..]); |
|||
this.Length = requiredCapacity; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a span containing the bytes written to the writer.
|
|||
/// </summary>
|
|||
/// <returns>A span containing the written bytes.</returns>
|
|||
public Span<byte> AsSpan() => this.buffer.Memory.Span[..this.Length]; |
|||
|
|||
/// <summary>
|
|||
/// Returns memory containing the bytes written to the writer.
|
|||
/// </summary>
|
|||
/// <returns>Memory containing the written bytes.</returns>
|
|||
public Memory<byte> AsMemory() => this.buffer.Memory[..this.Length]; |
|||
|
|||
private void EnsureCapacity(int requiredCapacity) |
|||
{ |
|||
if (requiredCapacity <= this.Capacity) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int newCapacity = Math.Max(requiredCapacity, checked(this.Capacity * 2)); |
|||
|
|||
IMemoryOwner<byte> previousBuffer = this.buffer; |
|||
this.buffer = allocator.Allocate<byte>(newCapacity); |
|||
|
|||
previousBuffer.Memory.Span[..this.Length].CopyTo(this.buffer.Memory.Span); |
|||
|
|||
previousBuffer.Dispose(); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
internal sealed class JxlAnimationHeader : IJxlFields |
|||
{ |
|||
public int TpsNumerator { get; set; } |
|||
|
|||
public int TpsDenominator { get; set; } |
|||
|
|||
public int LoopCount { get; set; } |
|||
|
|||
public bool ContainsTimecodes { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
/// <summary>
|
|||
/// Represents the JPEG XL Bit Depth image metadata.
|
|||
/// </summary>
|
|||
internal sealed class JxlBitDepthMetadata : IJxlFields |
|||
{ |
|||
private uint bitsPerSample; |
|||
private uint exponentBitsPerSample; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JxlBitDepthMetadata"/> class.
|
|||
/// </summary>
|
|||
public JxlBitDepthMetadata() => JxlBundle.Init(this); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether
|
|||
/// the original (uncompressed) samples are floating point or
|
|||
/// unsigned integer.
|
|||
/// </summary>
|
|||
public bool FloatingPointSample { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the bit depth of the original (uncompressed) image samples.
|
|||
/// Must be in the range [1, 32].
|
|||
/// </summary>
|
|||
public uint BitsPerSample |
|||
{ |
|||
get => this.bitsPerSample; |
|||
set => this.bitsPerSample = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// <para>
|
|||
/// Gets or sets floating point exponent bits of the original (uncompressed) image samples,
|
|||
/// only used if <see cref="FloatingPointSample"/> is <see langword="true"/>.
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// If used, the samples are floating point with:
|
|||
/// <list type="bullet">
|
|||
/// <item>1 sign bit</item>
|
|||
/// <item><see cref="ExponentBitsPerSample"/> exponent bits</item>
|
|||
/// <item>(<see cref="BitsPerSample"/> - <see cref="ExponentBitsPerSample"/> - 1) mantissa bits</item>
|
|||
/// </list>
|
|||
/// If used, <see cref="ExponentBitsPerSample"/> must be in the range
|
|||
/// [2, 8] and amount of mantissa bits must be in the range [2, 23].
|
|||
/// </para>
|
|||
/// </summary>
|
|||
public uint ExponentBitsPerSample |
|||
{ |
|||
get => this.exponentBitsPerSample; |
|||
set => this.exponentBitsPerSample = value; |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) |
|||
{ |
|||
if (!this.FloatingPointSample) |
|||
{ |
|||
bool successful = visitor.U32( |
|||
JxlFieldExpressions.Value(8u), |
|||
JxlFieldExpressions.Value(10u), |
|||
JxlFieldExpressions.Value(12u), |
|||
JxlFieldExpressions.BitsOffset(6u, 1u), |
|||
8u, |
|||
ref this.bitsPerSample); |
|||
|
|||
if (!successful) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.exponentBitsPerSample = 0; |
|||
} |
|||
else |
|||
{ |
|||
if (!visitor.U32( |
|||
JxlFieldExpressions.Value(32u), |
|||
JxlFieldExpressions.Value(16u), |
|||
JxlFieldExpressions.Value(24u), |
|||
JxlFieldExpressions.BitsOffset(6u, 1u), |
|||
32u, |
|||
ref this.bitsPerSample)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.exponentBitsPerSample--; |
|||
|
|||
if (!visitor.Bits(4, 7, ref this.exponentBitsPerSample)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.exponentBitsPerSample++; |
|||
} |
|||
|
|||
if (this.FloatingPointSample) |
|||
{ |
|||
if (this.exponentBitsPerSample is < 2 or > 8) |
|||
{ |
|||
DebugGuard.IsTrue(false, "Invalid exponent_bits_per_sample"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
int mantissaBits = (int)this.bitsPerSample - (int)this.exponentBitsPerSample - 1; |
|||
|
|||
if (mantissaBits is < 2 or > 23) |
|||
{ |
|||
DebugGuard.IsTrue(false, "Invalid bits_per_sample"); |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
else if (this.bitsPerSample > 31) |
|||
{ |
|||
DebugGuard.IsTrue(false, "Invalid bits_per_sample"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
internal sealed class JxlCodecMetadata |
|||
{ |
|||
public JxlImageMetadata? ImageMetadata { get; set; } |
|||
|
|||
public JxlSizeHeader? Size { get; set; } |
|||
|
|||
public JxlCustomTransformData? CustomTransformData { get; set; } |
|||
|
|||
public int XSize => this.Size?.XSize ?? 0; |
|||
|
|||
public int YSize => this.Size?.YSize ?? 0; |
|||
|
|||
public int GetOrientedPreviewXSize(bool keepOrientation) |
|||
{ |
|||
if (this.ImageMetadata!.Orientation > 4 && !keepOrientation) |
|||
{ |
|||
return this.ImageMetadata.PreviewSize.YSize; |
|||
} |
|||
|
|||
return this.ImageMetadata.PreviewSize.XSize; |
|||
} |
|||
|
|||
public int GetOrientedPreviewYSize(bool keepOrientation) |
|||
{ |
|||
if (this.ImageMetadata!.Orientation > 4 && !keepOrientation) |
|||
{ |
|||
return this.ImageMetadata.PreviewSize.XSize; |
|||
} |
|||
|
|||
return this.ImageMetadata.PreviewSize.YSize; |
|||
} |
|||
|
|||
public int GetOrientedXSize(bool keepOrientation) |
|||
{ |
|||
if (this.ImageMetadata!.Orientation > 4 && !keepOrientation) |
|||
{ |
|||
return this.YSize; |
|||
} |
|||
|
|||
return this.XSize; |
|||
} |
|||
|
|||
public int GetOrientedYSize(bool keepOrientation) |
|||
{ |
|||
if (this.ImageMetadata!.Orientation > 4 && !keepOrientation) |
|||
{ |
|||
return this.XSize; |
|||
} |
|||
|
|||
return this.YSize; |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
internal sealed class JxlCustomTransformData : IJxlFields |
|||
{ |
|||
public bool NonserializedXybEncoded { get; set; } |
|||
|
|||
public bool AllDefault { get; set; } |
|||
|
|||
public JxlOpsinInverseMatrix? OpsinInverseMatrix { get; set; } |
|||
|
|||
public int CustomWeightsMask { get; set; } |
|||
|
|||
public InlineArray15<float> Upsampling2Weights { get; set; } |
|||
|
|||
public InlineArray55<float> Upsampling4Weights { get; set; } |
|||
|
|||
public InlineArray210<float> Upsampling8Weights { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
internal enum JxlExifOrientation : byte |
|||
{ |
|||
Identity = 1, |
|||
FlipHorizontal = 2, |
|||
Rotate180 = 3, |
|||
FlipVertical = 4, |
|||
Transponse = 5, |
|||
Rotate90 = 6, |
|||
AntiTranspose = 7, |
|||
Rotate270 = 8 |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
internal enum JxlExtraChannel : byte |
|||
{ |
|||
Alpha, |
|||
Depth, |
|||
SpotColor, |
|||
SelectionMask, |
|||
Black, |
|||
Cfa, |
|||
Thermal, |
|||
Reserved0, |
|||
Reserved1, |
|||
Reserved2, |
|||
Reserved3, |
|||
Reserved4, |
|||
Reserved5, |
|||
Reserved6, |
|||
Reserved7, |
|||
Unknown, |
|||
Optional |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
internal sealed class JxlExtraChannelInfo : IJxlFields |
|||
{ |
|||
public bool AllDefault { get; set; } |
|||
|
|||
public JxlExtraChannel Type { get; set; } |
|||
|
|||
public JxlBitDepthMetadata? BitDepth { get; set; } |
|||
|
|||
public int DimensionShift { get; set; } |
|||
|
|||
public string? Name { get; set; } |
|||
|
|||
public bool AlphaAssociated { get; set; } |
|||
|
|||
public InlineArray4<float> SpotColor { get; set; } |
|||
|
|||
public int CfaChannel { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,152 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Cms; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
internal sealed class JxlImageMetadata : IJxlFields |
|||
{ |
|||
public bool AllDefault { get; set; } |
|||
|
|||
public JxlBitDepthMetadata? BitDepth { get; set; } |
|||
|
|||
public bool Modular16BitBufferSufficient { get; set; } // Otherwise, 32 is
|
|||
|
|||
public bool XybEncoded { get; set; } |
|||
|
|||
public JxlColorEncoding? ColorEncoding { get; set; } |
|||
|
|||
public int Orientation { get; set; } = 1; |
|||
|
|||
public bool HavePreview { get; set; } |
|||
|
|||
public bool HaveAnimation { get; set; } |
|||
|
|||
public bool HaveIntrinsicSize { get; set; } |
|||
|
|||
public JxlSizeHeader IntrinsicSize { get; set; } |
|||
|
|||
public JxlToneMapping? ToneMapping { get; set; } |
|||
|
|||
public int ExtraChannelCount { get; set; } |
|||
|
|||
public List<JxlExtraChannelInfo> ExtraChannels { get; set; } = []; |
|||
|
|||
public JxlPreviewHeader PreviewSize { get; set; } |
|||
|
|||
public JxlAnimationHeader Animation { get; set; } |
|||
|
|||
public long Extensions { get; set; } |
|||
|
|||
public bool NonserializedOnlyParseBasicInfos { get; set; } |
|||
|
|||
public float IntensityTarget |
|||
{ |
|||
get |
|||
{ |
|||
float intensityTarget = this.ToneMapping?.IntensityTarget ?? 0f; |
|||
|
|||
if (intensityTarget == 0f) |
|||
{ |
|||
throw new InvalidOperationException("Intensity target should be present"); |
|||
} |
|||
|
|||
return intensityTarget; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (this.ToneMapping != null) |
|||
{ |
|||
this.ToneMapping.IntensityTarget = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public int AlphaBits |
|||
{ |
|||
get |
|||
{ |
|||
JxlExtraChannelInfo? ec = this.FindExtraChannel(JxlExtraChannel.Alpha); |
|||
|
|||
if (ec == null) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
return ec.BitDepth?.BitsPerSample ?? 0; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
} |
|||
} |
|||
|
|||
public bool HasAlpha => this.AlphaBits != 0; |
|||
|
|||
public JxlExtraChannelInfo? FindExtraChannel(JxlExtraChannel type) |
|||
=> this.ExtraChannels.FirstOrDefault(eci => eci.Type == type); |
|||
|
|||
public JxlExifOrientation GetExifOrientation() => (JxlExifOrientation)this.Orientation; |
|||
|
|||
public void SetFloat16Samples() |
|||
{ |
|||
if (this.BitDepth != null) |
|||
{ |
|||
this.BitDepth.BitsPerSample = 16; |
|||
this.BitDepth.ExponentBitsPerSample = 5; |
|||
this.BitDepth.FloatingPointSample = true; |
|||
} |
|||
|
|||
this.Modular16BitBufferSufficient = false; |
|||
} |
|||
|
|||
public void SetFloat32Samples() |
|||
{ |
|||
if (this.BitDepth != null) |
|||
{ |
|||
this.BitDepth.BitsPerSample = 32; |
|||
this.BitDepth.ExponentBitsPerSample = 8; |
|||
this.BitDepth.FloatingPointSample = true; |
|||
} |
|||
|
|||
this.Modular16BitBufferSufficient = false; |
|||
} |
|||
|
|||
public void SetUIntSamples(int bits) |
|||
{ |
|||
if (this.BitDepth != null) |
|||
{ |
|||
this.BitDepth.BitsPerSample = bits; |
|||
this.BitDepth.ExponentBitsPerSample = 0; |
|||
this.BitDepth.FloatingPointSample = false; |
|||
} |
|||
|
|||
this.Modular16BitBufferSufficient = bits <= 12; |
|||
} |
|||
|
|||
public void SetIntensityTarget() |
|||
{ |
|||
JxlCustomTransferFunction? tf = this.ColorEncoding?.TransferFunction; |
|||
|
|||
if (tf is not null) |
|||
{ |
|||
if (tf.Value.IsPq) |
|||
{ |
|||
this.SetIntensityTarget(10000); |
|||
} |
|||
else if (tf.Value.IsHlg) |
|||
{ |
|||
this.SetIntensityTarget(1000); |
|||
} |
|||
else |
|||
{ |
|||
this.SetIntensityTarget(DefaultIntensityTarget); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
#pragma warning disable SA1401 // Fields should be private
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
internal sealed class JxlOpsinInverseMatrix : IJxlFields |
|||
{ |
|||
public InlineArray3<float> OpsinBiases; |
|||
|
|||
public InlineArray3<float> QuantBiases; |
|||
|
|||
public bool AllDefault { get; set; } |
|||
|
|||
public JxlMatrix3x3F InverseMatrix { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
internal sealed class JxlPreviewHeader : IJxlFields |
|||
{ |
|||
private bool div8; |
|||
private int ySizeDiv8; |
|||
private int ySize; |
|||
private int ratio; |
|||
private int xSizeDiv8; |
|||
private int xSize; |
|||
|
|||
public int YSize => this.div8 ? (this.ySizeDiv8 * 8) : this.ySize; |
|||
|
|||
public int XSize |
|||
{ |
|||
get |
|||
{ |
|||
if (this.ratio != 0) |
|||
{ |
|||
SignedRational signedRational = JxlAspectRatioHelpers.FixedAspectRatios(this.ratio); |
|||
|
|||
return JxlAspectRatioHelpers.MultiplyTruncate(signedRational, this.YSize); |
|||
} |
|||
|
|||
return this.div8 ? (this.xSizeDiv8 * 8) : this.xSize; |
|||
} |
|||
} |
|||
|
|||
public void Set(int x, int y) |
|||
{ |
|||
if (x == 0 || y == 0) |
|||
{ |
|||
throw new ArgumentException("Empty preview"); |
|||
} |
|||
|
|||
this.div8 = ((x % JxlFrameDimensions.BlockDimensions) | (y % JxlFrameDimensions.BlockDimensions)) == 0; |
|||
|
|||
if (this.div8) |
|||
{ |
|||
this.ySizeDiv8 = y / 8; |
|||
} |
|||
else |
|||
{ |
|||
this.ySize = y; |
|||
} |
|||
|
|||
this.ratio = JxlAspectRatioHelpers.FindAspectRatio(x, y); |
|||
|
|||
if (this.ratio == 0) |
|||
{ |
|||
if (this.div8) |
|||
{ |
|||
this.xSizeDiv8 = x / 8; |
|||
} |
|||
else |
|||
{ |
|||
this.xSize = x; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
internal sealed class JxlSizeHeader : IJxlFields |
|||
{ |
|||
private bool isSmall; |
|||
private int ySizeDiv8Minus1; |
|||
private int ySize; |
|||
private int ratio; |
|||
private int xSizeDiv8Minus1; |
|||
private int xSize; |
|||
|
|||
public int YSize => this.isSmall ? ((this.ySizeDiv8Minus1 + 1) * 8) : this.ySize; |
|||
|
|||
public int XSize |
|||
{ |
|||
get |
|||
{ |
|||
if (this.ratio != 0) |
|||
{ |
|||
SignedRational aspectRatio = JxlAspectRatioHelpers.FixedAspectRatios(this.ratio); |
|||
|
|||
return JxlAspectRatioHelpers.MultiplyTruncate(aspectRatio, this.YSize); |
|||
} |
|||
|
|||
return this.isSmall ? ((this.xSizeDiv8Minus1 + 1) * 8) : this.xSize; |
|||
} |
|||
} |
|||
|
|||
public void Set(int x, int y) |
|||
{ |
|||
if (x > int.MaxValue || y > int.MaxValue) |
|||
{ |
|||
throw new ArgumentException("Image too large"); |
|||
} |
|||
|
|||
if (x == 0 || y == 0) |
|||
{ |
|||
throw new ArgumentException("Empty image"); |
|||
} |
|||
|
|||
this.ratio = JxlAspectRatioHelpers.FindAspectRatio(x, y); |
|||
this.isSmall = y < 256 && (y % JxlFrameDimensions.BlockDimensions) == 0 |
|||
&& (this.ratio != 0 || (x <= 256 && (x % JxlFrameDimensions.BlockDimensions) == 0)); |
|||
|
|||
if (this.isSmall) |
|||
{ |
|||
this.ySizeDiv8Minus1 = (y / 8) - 1; |
|||
} |
|||
else |
|||
{ |
|||
this.ySize = y; |
|||
} |
|||
|
|||
if (this.ratio == 0) |
|||
{ |
|||
if (this.isSmall) |
|||
{ |
|||
this.xSizeDiv8Minus1 = (x / 8) - 1; |
|||
} |
|||
else |
|||
{ |
|||
this.xSize = x; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Fields; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; |
|||
|
|||
internal sealed class JxlToneMapping : IJxlFields |
|||
{ |
|||
public bool AllDefault { get; set; } |
|||
|
|||
public float IntensityTarget { get; set; } |
|||
|
|||
public float LowerBoundIntensityLevel { get; set; } |
|||
|
|||
public bool RelativeToMaxDisplay { get; set; } |
|||
|
|||
public float LinearBelow { get; set; } |
|||
|
|||
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
#pragma warning disable SA1649 // File name should match first type name
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl; |
|||
|
|||
/// <summary>
|
|||
/// Used by JxlCustomTransformData
|
|||
/// </summary>
|
|||
[InlineArray(55)] |
|||
internal struct InlineArray55<T> |
|||
{ |
|||
private T first; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Used by JpegQuantizationTable
|
|||
/// </summary>
|
|||
[InlineArray(64)] |
|||
internal struct InlineArray64<T> |
|||
{ |
|||
private T first; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Used by JxlCustomTransformData
|
|||
/// </summary>
|
|||
[InlineArray(210)] |
|||
internal struct InlineArray210<T> |
|||
{ |
|||
private T first; |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl; |
|||
|
|||
/// <summary>
|
|||
/// JPEG XL format
|
|||
/// </summary>
|
|||
public sealed class JxlFormat : IImageFormat |
|||
{ |
|||
/// <inheritdoc />
|
|||
public string Name => "JPEG XL"; |
|||
|
|||
/// <inheritdoc />
|
|||
public string DefaultMimeType => "image/jxl"; |
|||
|
|||
/// <inheritdoc />
|
|||
IEnumerable<string> IImageFormat.MimeTypes => new[] { "image/jxl" }; |
|||
|
|||
/// <inheritdoc />
|
|||
IEnumerable<string> IImageFormat.FileExtensions => new[] { "jxl" }; |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl; |
|||
|
|||
/// <summary>
|
|||
/// Checks if the first few bytes of a file represent
|
|||
/// JPEG XL.
|
|||
/// </summary>
|
|||
public sealed class JxlImageFormatDetector : IImageFormatDetector |
|||
{ |
|||
/// <summary>
|
|||
/// Gets file signature bytes which represent a container-based
|
|||
/// JPEG XL file.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> ContainerStart => |
|||
[ |
|||
0x00, 0x00, 0x00, 0x0C, |
|||
0x4A, 0x58, 0x4C, 0x20, |
|||
0x0D, 0x0A, 0x87, 0x0A, |
|||
]; |
|||
|
|||
/// <inheritdoc/>
|
|||
public int HeaderSize => 12; |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format) |
|||
{ |
|||
if (header.StartsWith([(byte)0xFF, (byte)0x0A])) |
|||
{ |
|||
// Just codestream.
|
|||
format = new JxlFormat(); |
|||
return true; |
|||
} |
|||
else if (header.SequenceEqual(ContainerStart)) |
|||
{ |
|||
// Container format.
|
|||
format = new JxlFormat(); |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
format = null; |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Metadata; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl; |
|||
|
|||
/// <summary>
|
|||
/// Image information specific to the JPEG XL format.
|
|||
/// </summary>
|
|||
public class JxlImageInfo : ImageInfo |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JxlImageInfo"/> class.
|
|||
/// </summary>
|
|||
/// <param name="size">Image size</param>
|
|||
/// <param name="metadata">Image metadata</param>
|
|||
public JxlImageInfo(Size size, ImageMetadata metadata) |
|||
: base(size, metadata) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl; |
|||
|
|||
internal static class JxlThrowHelper |
|||
{ |
|||
[DoesNotReturn] |
|||
public static void ThrowEndOfStream() => throw new EndOfStreamException(); |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a three-plane, 2D raster image of type <see cref="byte"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImage3B : JxlImage3<byte> |
|||
{ |
|||
public JxlImage3B() |
|||
{ |
|||
} |
|||
|
|||
public JxlImage3B(Configuration configuration, int xSize, int ySize) |
|||
: base(configuration, xSize, ySize) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a three-plane, 2D raster image of type <see cref="float"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImage3F : JxlImage3<float> |
|||
{ |
|||
public JxlImage3F() |
|||
{ |
|||
} |
|||
|
|||
public JxlImage3F(Configuration configuration, int xSize, int ySize) |
|||
: base(configuration, xSize, ySize) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a three-plane, 2D raster image of type <see cref="int"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImage3I : JxlImage3<int> |
|||
{ |
|||
public JxlImage3I() |
|||
{ |
|||
} |
|||
|
|||
public JxlImage3I(Configuration configuration, int xSize, int ySize) |
|||
: base(configuration, xSize, ySize) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a three-plane, 2D raster image of type <see cref="short"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImage3S : JxlImage3<short> |
|||
{ |
|||
public JxlImage3S() |
|||
{ |
|||
} |
|||
|
|||
public JxlImage3S(Configuration configuration, int xSize, int ySize) |
|||
: base(configuration, xSize, ySize) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a three-plane, 2D raster image of type <see cref="ushort"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImage3U : JxlImage3<ushort> |
|||
{ |
|||
public JxlImage3U() |
|||
{ |
|||
} |
|||
|
|||
public JxlImage3U(Configuration configuration, int xSize, int ySize) |
|||
: base(configuration, xSize, ySize) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="byte"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageB : JxlPlane<byte> |
|||
{ |
|||
public JxlImageB() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageB(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageB(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
|
|||
public Memory<byte> GetRowBytesMemory(int y) |
|||
{ |
|||
DebugGuard.MustBeLessThan(y, this.YSize, nameof(y)); |
|||
|
|||
Memory<byte> row = this.Bytes[(y * this.BytesPerRow)..]; |
|||
|
|||
return row; |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="float"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageF : JxlPlane<float> |
|||
{ |
|||
public JxlImageF() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageF(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageF(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="int"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageI : JxlPlane<int> |
|||
{ |
|||
public JxlImageI() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageI(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageI(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="short"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageS : JxlPlane<short> |
|||
{ |
|||
public JxlImageS() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageS(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageS(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="sbyte"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageSB : JxlPlane<sbyte> |
|||
{ |
|||
public JxlImageSB() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageSB(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageSB(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single-plane, 2D raster image of type <see cref="ushort"/>.
|
|||
/// </summary>
|
|||
internal sealed class JxlImageU : JxlPlane<ushort> |
|||
{ |
|||
public JxlImageU() |
|||
{ |
|||
} |
|||
|
|||
public JxlImageU(int width, int height) |
|||
: base(width, height) |
|||
{ |
|||
} |
|||
|
|||
public JxlImageU(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
: base(xSize, ySize) |
|||
=> this.Allocate(configuration, prePadding); |
|||
} |
|||
@ -0,0 +1,106 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory; |
|||
|
|||
// NOTE: Do not seal this class.
|
|||
internal class JxlImage3<T> : IDisposable |
|||
where T : unmanaged |
|||
{ |
|||
private const int PlaneCount = 3; |
|||
|
|||
private JxlPlane<T>[] planes = new JxlPlane<T>[3]; |
|||
|
|||
public JxlImage3() |
|||
{ |
|||
} |
|||
|
|||
public JxlImage3(Configuration configuration, int xSize, int ySize) |
|||
=> this.Allocate(configuration, xSize, ySize); |
|||
|
|||
public JxlImage3(JxlImage3<T> other) |
|||
{ |
|||
for (int i = 0; i < PlaneCount; i++) |
|||
{ |
|||
this.planes[i] = other.planes[i]; |
|||
} |
|||
} |
|||
|
|||
public int XSize => this.planes[0].XSize; |
|||
|
|||
public int YSize => this.planes[0].YSize; |
|||
|
|||
public int BytesPerRow => this.planes[0].BytesPerRow; |
|||
|
|||
public int PixelsPerRow => this.planes[0].PixelsPerRow; |
|||
|
|||
public Span<T> PlaneRow(int plane, int row) |
|||
{ |
|||
this.PlaneRowBoundsCheck(plane, row); |
|||
|
|||
int rowOffset = row * this.planes[0].BytesPerRow; |
|||
Span<T> rowSpan = MemoryMarshal.Cast<byte, T>(this.planes[plane].BytesSpan[rowOffset..]); |
|||
|
|||
return rowSpan; |
|||
} |
|||
|
|||
public Span<T> PlaneRow(Rectangle rectangle, int c, int y) |
|||
{ |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(y + rectangle.Top, 0, nameof(y)); |
|||
|
|||
return this.PlaneRow(c, y + rectangle.Top)[rectangle.Left..]; |
|||
} |
|||
|
|||
public JxlPlane<T> Plane(int index) => this.planes[index]; |
|||
|
|||
public void Swap(JxlImage3<T> other) |
|||
{ |
|||
for (int i = 0; i < PlaneCount; i++) |
|||
{ |
|||
other.planes[i].Swap(this.planes[i]); |
|||
} |
|||
} |
|||
|
|||
public static JxlImage3<T> Create(Configuration configuration, int xSize, int ySize) |
|||
=> new(configuration, xSize, ySize); |
|||
|
|||
public void Allocate(Configuration configuration, int xSize, int ySize) |
|||
{ |
|||
JxlPlane<T> plane0 = JxlPlane<T>.Create(configuration, xSize, ySize); |
|||
JxlPlane<T> plane1 = JxlPlane<T>.Create(configuration, xSize, ySize); |
|||
JxlPlane<T> plane2 = JxlPlane<T>.Create(configuration, xSize, ySize); |
|||
|
|||
this.planes = [plane0, plane1, plane2]; |
|||
} |
|||
|
|||
public bool ShrinkTo(int x, int y) |
|||
{ |
|||
for (int i = 0; i < PlaneCount; i++) |
|||
{ |
|||
if (!this.planes[i].ShrinkTo(x, y)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private void PlaneRowBoundsCheck(int c, int y) |
|||
{ |
|||
DebugGuard.MustBeLessThan(c, PlaneCount, nameof(c)); |
|||
DebugGuard.MustBeLessThan(y, this.YSize, nameof(y)); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
foreach (JxlPlane<T> plane in this.planes) |
|||
{ |
|||
plane.Dispose(); |
|||
} |
|||
|
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,182 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory; |
|||
|
|||
/// <summary>
|
|||
/// Base class for a single-plane image.
|
|||
/// </summary>
|
|||
internal class JxlPlaneBase : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Underlying bytes
|
|||
/// </summary>
|
|||
private IMemoryOwner<byte>? bytes; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JxlPlaneBase"/> class.
|
|||
/// </summary>
|
|||
/// <param name="xSize">Plane width</param>
|
|||
/// <param name="ySize">Plane height</param>
|
|||
/// <param name="sizeOfT">The size of each pixel in bytes.</param>
|
|||
public JxlPlaneBase(int xSize, int ySize, int sizeOfT) |
|||
{ |
|||
this.XSize = xSize; |
|||
this.YSize = ySize; |
|||
this.OriginalXSize = xSize; |
|||
this.OriginalYSize = ySize; |
|||
this.BytesPerRow = 0; |
|||
this.Size = sizeOfT; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JxlPlaneBase"/> class with empty values.
|
|||
/// </summary>
|
|||
public JxlPlaneBase() |
|||
: this(0, 0, 0) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of bytes per row.
|
|||
/// </summary>
|
|||
public int BytesPerRow { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the width of the image.
|
|||
/// </summary>
|
|||
public int XSize { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the height of the image.
|
|||
/// </summary>
|
|||
public int YSize { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the underlying bytes of this image as a Memory<T>.
|
|||
/// </summary>
|
|||
public Memory<byte> Bytes => |
|||
#if DEBUG
|
|||
this.bytes?.Memory ?? throw new InvalidOperationException("Bytes are missing"); |
|||
#else
|
|||
return this.bytes!.Memory; |
|||
#endif
|
|||
|
|||
/// <summary>
|
|||
/// Gets the underlying bytes of this image as a Span<T>.
|
|||
/// </summary>
|
|||
public Span<byte> BytesSpan => this.Bytes.Span; |
|||
|
|||
protected int Size { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the width that was initially assigned. For example, if the image gets shrinked,
|
|||
/// the XSize YSize properties get changed while this property will stay same.
|
|||
/// </summary>
|
|||
protected int OriginalXSize { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the height that was initially assigned. For example, if the image gets shrinked,
|
|||
/// the XSize YSize properties get changed while this property will stay same.
|
|||
/// </summary>
|
|||
protected int OriginalYSize { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Allocates the underlying memory for the plane.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration which has a memory allocator used to allocate memory.</param>
|
|||
/// <param name="prePadding">Padding</param>
|
|||
/// <returns>Status of allocation.</returns>
|
|||
public bool Allocate(Configuration configuration, int prePadding) |
|||
{ |
|||
if (this.bytes != null || this.BytesPerRow != 0) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (this.XSize == 0 || this.YSize == 0) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
int totalBytes = unchecked(this.YSize * this.BytesPerRow); |
|||
|
|||
this.bytes = configuration.MemoryAllocator.Allocate<byte>(totalBytes + (prePadding * this.Size)); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Shrinks the image so its width is equal to <paramref name="x"/> and its height is
|
|||
/// equal to <paramref name="y"/>.
|
|||
/// </summary>
|
|||
/// <param name="x">The output width</param>
|
|||
/// <param name="y">The output height</param>
|
|||
/// <returns>Status of the shrinking operation.</returns>
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// This method can only shrink memory. It cannot expand it.
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// When shrinking, the underlying memory does not get resized.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool ShrinkTo(int x, int y) |
|||
{ |
|||
if (x <= this.OriginalXSize || y <= this.OriginalYSize) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
DebugGuard.MustBeLessThanOrEqualTo(x, this.OriginalXSize, nameof(x)); |
|||
DebugGuard.MustBeLessThanOrEqualTo(y, this.OriginalYSize, nameof(y)); |
|||
|
|||
this.XSize = x; |
|||
this.YSize = y; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Base function to return the span for a specified row as a generic <T>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the row.</typeparam>
|
|||
/// <param name="y">The index of the row to get the span for.</param>
|
|||
/// <returns>A span which covers the row memory.</returns>
|
|||
protected Span<T> GetRowBase<T>(int y) |
|||
where T : unmanaged |
|||
{ |
|||
DebugGuard.MustBeLessThan(y, this.YSize, nameof(y)); |
|||
|
|||
Span<byte> row = this.Bytes.Span[(y * this.BytesPerRow)..]; |
|||
return MemoryMarshal.Cast<byte, T>(row); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Swaps properties & data of this image with the specified image.
|
|||
/// </summary>
|
|||
/// <param name="other">The other image to swap with.</param>
|
|||
public void Swap(JxlPlaneBase other) |
|||
{ |
|||
(this.XSize, other.XSize) = (other.XSize, this.XSize); |
|||
(this.YSize, other.YSize) = (other.YSize, this.YSize); |
|||
(this.OriginalXSize, other.OriginalXSize) = (other.OriginalXSize, this.OriginalXSize); |
|||
(this.OriginalYSize, other.OriginalYSize) = (other.OriginalYSize, this.OriginalYSize); |
|||
(this.BytesPerRow, other.BytesPerRow) = (other.BytesPerRow, this.BytesPerRow); |
|||
(this.bytes, other.bytes) = (other.bytes, this.bytes); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Releases all underlying memory used by this plane.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.bytes?.Dispose(); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jxl.Processing; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jxl.Memory; |
|||
|
|||
/// <summary>
|
|||
/// A generic version of a 2D single-plane JPEG XL image.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of each pixel.</typeparam>
|
|||
internal class JxlPlane<T> : JxlPlaneBase |
|||
where T : unmanaged |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JxlPlane{T}"/> class.
|
|||
/// </summary>
|
|||
public JxlPlane() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JxlPlane{T}"/> class with the specified width and height.
|
|||
/// </summary>
|
|||
/// <param name="width">Plane width.</param>
|
|||
/// <param name="height">Plane height</param>
|
|||
public unsafe JxlPlane(int width, int height) |
|||
: base(width, height, sizeof(T)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of pixels per row.
|
|||
/// </summary>
|
|||
public unsafe int PixelsPerRow => this.BytesPerRow / sizeof(T); |
|||
|
|||
/// <summary>
|
|||
/// Allocates a new plane.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration which contains a memory allocator.</param>
|
|||
/// <param name="xSize">Plane width</param>
|
|||
/// <param name="ySize">Plane height</param>
|
|||
/// <param name="prePadding">Padding</param>
|
|||
/// <returns>A new allocated plane</returns>
|
|||
/// <exception cref="InvalidOperationException">Thrown when allocation fails.</exception>
|
|||
public static JxlPlane<T> Create(Configuration configuration, int xSize, int ySize, int prePadding = 0) |
|||
{ |
|||
JxlPlane<T> plane = new(xSize, ySize); |
|||
|
|||
bool allocated = plane.Allocate(configuration, prePadding); |
|||
|
|||
if (!allocated) |
|||
{ |
|||
throw new InvalidOperationException("Failed to allocate a JPEG XL plane"); |
|||
} |
|||
|
|||
return plane; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a span for the specified row.
|
|||
/// </summary>
|
|||
/// <param name="y">The row index.</param>
|
|||
/// <returns>A span which covers memory for the specified row.</returns>
|
|||
public Span<T> GetRow(int y) => this.GetRowBase<T>(y); |
|||
|
|||
/// <summary>
|
|||
/// Returns a span for the specified row within the specified rectangle bounds.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The bounds.</param>
|
|||
/// <param name="y">The row index.</param>
|
|||
/// <returns>A span which covers memory for the specified row with the rectangle offsets.</returns>
|
|||
public Span<T> GetRow(Rectangle rectangle, int y) |
|||
{ |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(y + rectangle.Top, 0, nameof(y)); |
|||
|
|||
return this.GetRow(y + rectangle.Top)[rectangle.Left..]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks if the specified rectangle is within the bounds image.
|
|||
/// </summary>
|
|||
/// <param name="rectangle">The input rectangle.</param>
|
|||
/// <returns>Boolean indicating whether the rectangle is inside.</returns>
|
|||
public bool IsRectangleInside(Rectangle rectangle) => rectangle.Contains(this.GetRectangle()); |
|||
|
|||
/// <summary>
|
|||
/// Returns the rectangle for this image bounds.
|
|||
/// </summary>
|
|||
/// <returns>A rectangle with x,y=0,0 width,height=XSize,YSize.</returns>
|
|||
public Rectangle GetRectangle() => new(0, 0, this.XSize, this.YSize); |
|||
|
|||
/// <summary>
|
|||
/// Fills everything in this image with 0.
|
|||
/// </summary>
|
|||
public void Clear() => JxlImageOperations.ZeroFillImage(this); |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue