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