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(); +}