From 6fea920e7f20db23b57f7e7b4378d0a6ec265f2c Mon Sep 17 00:00:00 2001
From: winscripter <142818255+winscripter@users.noreply.github.com>
Date: Wed, 15 Jul 2026 17:52:21 +0400
Subject: [PATCH] Add metadata models
---
src/ImageSharp/Formats/Jxl/IO/IJxlFields.cs | 17 +++
.../Formats/Jxl/Metadata/InlineArrays.cs | 41 ++++++
.../Formats/Jxl/Metadata/JxlBitDepth.cs | 42 ++++++
.../Formats/Jxl/Metadata/JxlCodecMetadata.cs | 57 ++++++++
.../Jxl/Metadata/JxlCustomTransformData.cs | 25 ++++
.../Jxl/Metadata/JxlExifOrientation.cs | 16 +++
.../Formats/Jxl/Metadata/JxlExtraChannel.cs | 25 ++++
.../Jxl/Metadata/JxlExtraChannelInfo.cs | 27 ++++
.../Formats/Jxl/Metadata/JxlImageMetadata.cs | 126 ++++++++++++++++++
.../Jxl/Metadata/JxlOpsinInverseMatrix.cs | 19 +++
.../Formats/Jxl/Metadata/JxlToneMapping.cs | 21 +++
11 files changed, 416 insertions(+)
create mode 100644 src/ImageSharp/Formats/Jxl/IO/IJxlFields.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Metadata/InlineArrays.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Metadata/JxlBitDepth.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Metadata/JxlCodecMetadata.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Metadata/JxlCustomTransformData.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Metadata/JxlExifOrientation.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Metadata/JxlExtraChannel.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Metadata/JxlExtraChannelInfo.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Metadata/JxlImageMetadata.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Metadata/JxlOpsinInverseMatrix.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Metadata/JxlToneMapping.cs
diff --git a/src/ImageSharp/Formats/Jxl/IO/IJxlFields.cs b/src/ImageSharp/Formats/Jxl/IO/IJxlFields.cs
new file mode 100644
index 0000000000..191cc96a3e
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/IO/IJxlFields.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Jxl.IO;
+
+///
+/// Abstracts enumeration of all fields into a visitor.
+///
+internal interface IJxlFields
+{
+ ///
+ /// Visits all fields into the specified JXL visitor.
+ ///
+ /// The visitor to use to visit all fields.
+ /// Status of the visit operation.
+ public bool Visit(JxlVisitor visitor);
+}
diff --git a/src/ImageSharp/Formats/Jxl/Metadata/InlineArrays.cs b/src/ImageSharp/Formats/Jxl/Metadata/InlineArrays.cs
new file mode 100644
index 0000000000..f1f9d3037b
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Metadata/InlineArrays.cs
@@ -0,0 +1,41 @@
+// 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.Metadata;
+
+[InlineArray(3)]
+internal struct InlineArray3
+{
+ private T first;
+}
+
+///
+/// Used by JxlCustomTransformData
+///
+[InlineArray(15)]
+internal struct InlineArray15
+{
+ private T first;
+}
+
+///
+/// Used by JxlCustomTransformData
+///
+[InlineArray(55)]
+internal struct InlineArray55
+{
+ private T first;
+}
+
+///
+/// Used by JxlCustomTransformData
+///
+[InlineArray(210)]
+internal struct InlineArray210
+{
+ private T first;
+}
diff --git a/src/ImageSharp/Formats/Jxl/Metadata/JxlBitDepth.cs b/src/ImageSharp/Formats/Jxl/Metadata/JxlBitDepth.cs
new file mode 100644
index 0000000000..0df657f44d
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Metadata/JxlBitDepth.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Jxl.IO;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Metadata;
+
+internal sealed class JxlBitDepth : IJxlFields
+{
+ ///
+ /// Gets or sets a value indicating whether
+ /// the original (uncompressed) samples are floating point or
+ /// unsigned integer.
+ ///
+ public bool FloatingPointSample { get; set; }
+
+ ///
+ /// Gets or sets the bit depth of the original (uncompressed) image samples.
+ /// Must be in the range [1, 32].
+ ///
+ public int BitsPerSample { get; set; }
+
+ ///
+ ///
+ /// Gets or sets floating point exponent bits of the original (uncompressed) image samples,
+ /// only used if is .
+ ///
+ ///
+ /// If used, the samples are floating point with:
+ ///
+ /// - 1 sign bit
+ /// - exponent bits
+ /// - ( - - 1) mantissa bits
+ ///
+ /// If used, must be in the range
+ /// [2, 8] and amount of mantissa bits must be in the range [2, 23].
+ ///
+ ///
+ public int ExponentBitsPerSample { get; set; }
+
+ public bool Visit(JxlVisitor visitor) => throw new NotImplementedException();
+}
diff --git a/src/ImageSharp/Formats/Jxl/Metadata/JxlCodecMetadata.cs b/src/ImageSharp/Formats/Jxl/Metadata/JxlCodecMetadata.cs
new file mode 100644
index 0000000000..d26d79d9a5
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Metadata/JxlCodecMetadata.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Metadata;
+
+internal sealed class JxlCodecMetadata
+{
+ public JxlImageMetadata? ImageMetadata { get; set; }
+
+ public SizeHeader Size { get; set; }
+
+ public JxlCustomTransformData? CustomTransformData { get; set; }
+
+ public int XSize => this.Size.XSize;
+
+ public int YSize => this.Size.YSize;
+
+ 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;
+ }
+}
diff --git a/src/ImageSharp/Formats/Jxl/Metadata/JxlCustomTransformData.cs b/src/ImageSharp/Formats/Jxl/Metadata/JxlCustomTransformData.cs
new file mode 100644
index 0000000000..78c0e9ba81
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Metadata/JxlCustomTransformData.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Jxl.IO;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.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 Upsampling2Weights { get; set; }
+
+ public InlineArray55 Upsampling4Weights { get; set; }
+
+ public InlineArray210 Upsampling8Weights { get; set; }
+
+ public bool Visit(JxlVisitor visitor) => throw new NotImplementedException();
+}
diff --git a/src/ImageSharp/Formats/Jxl/Metadata/JxlExifOrientation.cs b/src/ImageSharp/Formats/Jxl/Metadata/JxlExifOrientation.cs
new file mode 100644
index 0000000000..9146c2bfa2
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Metadata/JxlExifOrientation.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Metadata;
+
+internal enum JxlExifOrientation
+{
+ Identity = 1,
+ FlipHorizontal = 2,
+ Rotate180 = 3,
+ FlipVertical = 4,
+ Transponse = 5,
+ Rotate90 = 6,
+ AntiTranspose = 7,
+ Rotate270 = 8
+}
diff --git a/src/ImageSharp/Formats/Jxl/Metadata/JxlExtraChannel.cs b/src/ImageSharp/Formats/Jxl/Metadata/JxlExtraChannel.cs
new file mode 100644
index 0000000000..d8e62dc931
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Metadata/JxlExtraChannel.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Metadata;
+
+internal enum JxlExtraChannel
+{
+ Alpha,
+ Depth,
+ SpotColor,
+ SelectionMask,
+ Black,
+ Cfa,
+ Thermal,
+ Reserved0,
+ Reserved1,
+ Reserved2,
+ Reserved3,
+ Reserved4,
+ Reserved5,
+ Reserved6,
+ Reserved7,
+ Unknown,
+ Optional
+}
diff --git a/src/ImageSharp/Formats/Jxl/Metadata/JxlExtraChannelInfo.cs b/src/ImageSharp/Formats/Jxl/Metadata/JxlExtraChannelInfo.cs
new file mode 100644
index 0000000000..0279c2d7ab
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Metadata/JxlExtraChannelInfo.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Jxl.IO;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Metadata;
+
+internal sealed class JxlExtraChannelInfo : IJxlFields
+{
+ public bool AllDefault { get; set; }
+
+ public JxlExtraChannel Type { get; set; }
+
+ public JxlBitDepth? BitDepth { get; set; }
+
+ public int DimensionShift { get; set; }
+
+ public string? Name { get; set; }
+
+ public bool AlphaAssociated { get; set; }
+
+ public InlineArray4 SpotColor { get; set; }
+
+ public int CfaChannel { get; set; }
+
+ public bool Visit(JxlVisitor visitor) => throw new NotImplementedException();
+}
diff --git a/src/ImageSharp/Formats/Jxl/Metadata/JxlImageMetadata.cs b/src/ImageSharp/Formats/Jxl/Metadata/JxlImageMetadata.cs
new file mode 100644
index 0000000000..633a01aed6
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Metadata/JxlImageMetadata.cs
@@ -0,0 +1,126 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Diagnostics;
+using SixLabors.ImageSharp.Formats.Jxl.IO;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Metadata;
+
+internal sealed class JxlImageMetadata : IJxlFields
+{
+ public bool AllDefault { get; set; }
+
+ public JxlBitDepth? 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 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;
+
+ Debug.Assert(intensityTarget != 0f, "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;
+ }
+}
diff --git a/src/ImageSharp/Formats/Jxl/Metadata/JxlOpsinInverseMatrix.cs b/src/ImageSharp/Formats/Jxl/Metadata/JxlOpsinInverseMatrix.cs
new file mode 100644
index 0000000000..ccf2b25303
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Metadata/JxlOpsinInverseMatrix.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Jxl.IO;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Metadata;
+
+internal sealed class JxlOpsinInverseMatrix : IJxlFields
+{
+ public bool AllDefault { get; set; }
+
+ public JxlMatrix3x3 InverseMatrix { get; set; }
+
+ public InlineArray3 OpsinBiases { get; set; }
+
+ public InlineArray4 QuantBiases { get; set; }
+
+ public bool Visit(JxlVisitor visitor) => throw new NotImplementedException();
+}
diff --git a/src/ImageSharp/Formats/Jxl/Metadata/JxlToneMapping.cs b/src/ImageSharp/Formats/Jxl/Metadata/JxlToneMapping.cs
new file mode 100644
index 0000000000..1698b07016
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Metadata/JxlToneMapping.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Jxl.IO;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.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();
+}