Browse Source

Implement field visitor for metadata

pull/3153/head
winscripter 3 weeks ago
parent
commit
37766b8d1a
  1. 131
      src/ImageSharp/Formats/Jxl/IO/Metadata/JxlExtraChannelInfo.cs
  2. 278
      src/ImageSharp/Formats/Jxl/IO/Metadata/JxlImageMetadata.cs
  3. 70
      src/ImageSharp/Formats/Jxl/IO/Metadata/JxlOpsinInverseMatrix.cs
  4. 90
      src/ImageSharp/Formats/Jxl/IO/Metadata/JxlToneMapping.cs

131
src/ImageSharp/Formats/Jxl/IO/Metadata/JxlExtraChannelInfo.cs

@ -1,39 +1,152 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jxl.Fields; using SixLabors.ImageSharp.Formats.Jxl.Fields;
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata;
internal sealed class JxlExtraChannelInfo : IJxlFields internal sealed class JxlExtraChannelInfo : IJxlFields
{ {
public bool AllDefault { get; set; } private bool allDefault;
private JxlExtraChannel type;
private JxlBitDepthMetadata? bitDepth;
private int dimensionShift;
private string? name;
private bool alphaAssociated;
private InlineArray4<float> spotColor;
private int cfaChannel;
public JxlExtraChannel Type { get; set; } public bool AllDefault
{
get => this.allDefault;
set => this.allDefault = value;
}
public JxlExtraChannel Type
{
get => this.type;
set => this.type = value;
}
public JxlBitDepthMetadata? BitDepth { get; set; } public JxlBitDepthMetadata? BitDepth
{
get => this.bitDepth;
set => this.bitDepth = value;
}
public int DimensionShift { get; set; } public int DimensionShift
{
get => this.dimensionShift;
set => this.dimensionShift = value;
}
public string? Name { get; set; } public string? Name
{
get => this.name;
set => this.name = value;
}
public bool AlphaAssociated { get; set; } public bool AlphaAssociated
{
get => this.alphaAssociated;
set => this.alphaAssociated = value;
}
public InlineArray4<float> SpotColor { get; set; } public InlineArray4<float> SpotColor
{
get => this.spotColor;
set => this.spotColor = value;
}
public int CfaChannel { get; set; } public int CfaChannel
{
get => this.cfaChannel;
set => this.cfaChannel = value;
}
public bool Visit(JxlVisitor visitor) public bool Visit(JxlVisitor visitor)
{ {
if (visitor.AllDefault(this, ref this.allDefault)) if (visitor.AllDefault(this, ref this.allDefault))
{ {
// Overwrite all serialized fields, but not any nonserialized_*.
visitor.SetDefault(this); visitor.SetDefault(this);
return true; return true;
} }
if (!visitor.Enum(JxlExtraChannel.Alpha, ref this.Type)) // General
if (!visitor.Enum(JxlExtraChannel.Alpha, ref this.type))
{
return false;
}
if (!visitor.VisitNested(this.bitDepth!))
{ {
return false; return false;
} }
if (!visitor.U32(
JxlFieldExpressions.Value(0u),
JxlFieldExpressions.Value(3u),
JxlFieldExpressions.Value(4u),
JxlFieldExpressions.BitsOffset(3u, 1u),
0u,
ref Unsafe.As<int, uint>(ref this.dimensionShift)))
{
return false;
}
if ((1u << this.dimensionShift) > 8u)
{
return false;
}
if (!VisitNameString(visitor, ref this.name))
{
return false;
}
// Conditional
if (visitor.Conditional(this.type == JxlExtraChannel.Alpha))
{
if (!visitor.Boolean(false, ref this.alphaAssociated))
{
return false;
}
}
if (visitor.Conditional(this.type == JxlExtraChannel.SpotColor))
{
for (int i = 0; i < 4; i++)
{
if (!visitor.F16(0F, ref this.spotColor[i]))
{
return false;
}
}
}
if (visitor.Conditional(this.type == JxlExtraChannel.Cfa))
{
if (!visitor.U32(
JxlFieldExpressions.Value(1u),
JxlFieldExpressions.Bits(2u),
JxlFieldExpressions.BitsOffset(4u, 3u),
JxlFieldExpressions.BitsOffset(8u, 19u),
1u,
ref Unsafe.As<int, uint>(ref this.cfaChannel)))
{
return false;
}
}
if (this.type is JxlExtraChannel.Unknown or
>= JxlExtraChannel.Reserved0 and
<= JxlExtraChannel.Reserved7)
{
return false;
}
return true;
} }
} }

278
src/ImageSharp/Formats/Jxl/IO/Metadata/JxlImageMetadata.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jxl.Cms; using SixLabors.ImageSharp.Formats.Jxl.Cms;
using SixLabors.ImageSharp.Formats.Jxl.Fields; using SixLabors.ImageSharp.Formats.Jxl.Fields;
@ -8,37 +9,120 @@ namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata;
internal sealed class JxlImageMetadata : IJxlFields internal sealed class JxlImageMetadata : IJxlFields
{ {
public bool AllDefault { get; set; } private bool allDefault;
private JxlBitDepthMetadata? bitDepth;
private bool modular16BitBufferSufficient;
private bool xybEncoded;
private JxlColorEncoding? colorEncoding;
private int orientation = 1;
private bool havePreview;
private bool haveAnimation;
private bool haveIntrinsicSize;
private JxlSizeHeader intrinsicSize = new();
private JxlToneMapping? toneMapping;
private int extraChannelCount;
private List<JxlExtraChannelInfo> extraChannels = [];
private JxlPreviewHeader previewSize = new();
private JxlAnimationHeader animation = new();
private long extensions;
public JxlImageMetadata() => JxlBundle.Init(this);
public bool AllDefault
{
get => this.allDefault;
set => this.allDefault = value;
}
public JxlBitDepthMetadata? BitDepth { get; set; } public JxlBitDepthMetadata? BitDepth
{
get => this.bitDepth;
set => this.bitDepth = value;
}
public bool Modular16BitBufferSufficient { get; set; } // Otherwise, 32 is public bool Modular16BitBufferSufficient
{
get => this.modular16BitBufferSufficient;
set => this.modular16BitBufferSufficient = value;
}
public bool XybEncoded { get; set; } public bool XybEncoded
{
get => this.xybEncoded;
set => this.xybEncoded = value;
}
public JxlColorEncoding? ColorEncoding { get; set; } public JxlColorEncoding? ColorEncoding
{
get => this.colorEncoding;
set => this.colorEncoding = value;
}
public int Orientation { get; set; } = 1; public int Orientation
{
get => this.orientation;
set => this.orientation = value;
}
public bool HavePreview { get; set; } public bool HavePreview
{
get => this.havePreview;
set => this.havePreview = value;
}
public bool HaveAnimation { get; set; } public bool HaveAnimation
{
get => this.haveAnimation;
set => this.haveAnimation = value;
}
public bool HaveIntrinsicSize { get; set; } public bool HaveIntrinsicSize
{
get => this.haveIntrinsicSize;
set => this.haveIntrinsicSize = value;
}
public JxlSizeHeader IntrinsicSize { get; set; } = new(); public JxlSizeHeader IntrinsicSize
{
get => this.intrinsicSize;
set => this.intrinsicSize = value;
}
public JxlToneMapping? ToneMapping { get; set; } public JxlToneMapping? ToneMapping
{
get => this.toneMapping;
set => this.toneMapping = value;
}
public int ExtraChannelCount { get; set; } public int ExtraChannelCount
{
get => this.extraChannelCount;
set => this.extraChannelCount = value;
}
public List<JxlExtraChannelInfo> ExtraChannels { get; set; } = []; public List<JxlExtraChannelInfo> ExtraChannels
{
get => this.extraChannels;
set => this.extraChannels = value;
}
public JxlPreviewHeader PreviewSize { get; set; } = new(); public JxlPreviewHeader PreviewSize
{
get => this.previewSize;
set => this.previewSize = value;
}
public JxlAnimationHeader Animation { get; set; } = new(); public JxlAnimationHeader Animation
{
get => this.animation;
set => this.animation = value;
}
public long Extensions { get; set; } public long Extensions
{
get => this.extensions;
set => this.extensions = value;
}
public bool NonserializedOnlyParseBasicInfos { get; set; } public bool NonserializedOnlyParseBasicInfos { get; set; }
@ -148,5 +232,165 @@ internal sealed class JxlImageMetadata : IJxlFields
} }
} }
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); public bool Visit(JxlVisitor visitor)
{
if (visitor.AllDefault(this, ref this.allDefault))
{
// Overwrite all serialized fields, but not any nonserialized_*.
visitor.SetDefault(this);
return true;
}
// Bundle.AllDefault does not allow usage when reading because it may abort
// when a codestream has invalid values. When reading, extraFields is
// overwritten below, so AllDefault is not needed.
bool toneMappingDefault = !visitor.IsReading && JxlBundle.AllDefault(this.toneMapping!);
bool extraFields = this.orientation != 1 ||
this.havePreview ||
this.haveAnimation ||
this.haveIntrinsicSize ||
!toneMappingDefault;
if (!visitor.Boolean(false, ref extraFields))
{
return false;
}
if (visitor.Conditional(extraFields))
{
this.orientation--;
if (!visitor.Bits(3, 0, ref Unsafe.As<int, uint>(ref this.orientation)))
{
return false;
}
this.orientation++;
// No bounds checking is necessary because exactly 3 bits are read.
if (!visitor.Boolean(false, ref this.haveIntrinsicSize))
{
return false;
}
if (visitor.Conditional(this.haveIntrinsicSize))
{
if (!visitor.VisitNested(this.intrinsicSize))
{
return false;
}
}
if (!visitor.Boolean(false, ref this.havePreview))
{
return false;
}
if (visitor.Conditional(this.havePreview))
{
if (!visitor.VisitNested(this.previewSize))
{
return false;
}
}
if (!visitor.Boolean(false, ref this.haveAnimation))
{
return false;
}
if (visitor.Conditional(this.haveAnimation))
{
if (!visitor.VisitNested(this.animation))
{
return false;
}
}
}
else
{
this.orientation = 1;
this.haveIntrinsicSize = false;
this.havePreview = false;
this.haveAnimation = false;
}
if (!visitor.VisitNested(this.bitDepth!))
{
return false;
}
if (!visitor.Boolean(true, ref this.modular16BitBufferSufficient))
{
return false;
}
this.extraChannelCount = this.extraChannels.Count;
if (!visitor.U32(
JxlFieldExpressions.Value(0u),
JxlFieldExpressions.Value(1u),
JxlFieldExpressions.BitsOffset(4u, 2u),
JxlFieldExpressions.BitsOffset(12u, 1u),
0u,
ref Unsafe.As<int, uint>(ref this.extraChannelCount)))
{
return false;
}
if (visitor.Conditional(this.extraChannelCount != 0))
{
if (visitor.IsReading)
{
this.extraChannels.Clear();
this.extraChannels.Capacity = this.extraChannelCount;
for (int i = 0; i < this.extraChannelCount; i++)
{
this.extraChannels.Add(new JxlExtraChannelInfo());
}
}
for (int i = 0; i < this.extraChannels.Count; i++)
{
if (!visitor.VisitNested(this.extraChannels[i]))
{
return false;
}
}
}
if (!visitor.Boolean(true, ref this.xybEncoded))
{
return false;
}
if (!visitor.VisitNested(this.colorEncoding!))
{
return false;
}
if (visitor.Conditional(extraFields))
{
if (!visitor.VisitNested(this.toneMapping!))
{
return false;
}
}
// Treat as if only the fields up to extra channels exist.
if (visitor.IsReading && this.NonserializedOnlyParseBasicInfos)
{
return true;
}
if (!visitor.BeginExtensions(ref Unsafe.As<long, ulong>(ref this.extensions)))
{
return false;
}
// Extensions: in chronological order of being added to the format.
return visitor.EndExtensions();
}
} }

70
src/ImageSharp/Formats/Jxl/IO/Metadata/JxlOpsinInverseMatrix.cs

@ -11,13 +11,73 @@ namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata;
internal sealed class JxlOpsinInverseMatrix : IJxlFields internal sealed class JxlOpsinInverseMatrix : IJxlFields
{ {
public InlineArray3<float> OpsinBiases; private InlineArray3<float> opsinBiases;
private InlineArray3<float> quantBiases;
private bool allDefault;
private JxlMatrix3x3F inverseMatrix;
public InlineArray3<float> QuantBiases; public InlineArray3<float> OpsinBiases
{
get => this.opsinBiases;
set => this.opsinBiases = value;
}
public bool AllDefault { get; set; } public InlineArray3<float> QuantBiases
{
get => this.quantBiases;
set => this.quantBiases = value;
}
public JxlMatrix3x3F InverseMatrix { get; set; } public bool AllDefault
{
get => this.allDefault;
set => this.allDefault = value;
}
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); public JxlMatrix3x3F InverseMatrix
{
get => this.inverseMatrix;
set => this.inverseMatrix = value;
}
public bool Visit(JxlVisitor visitor)
{
if (visitor.AllDefault(this, ref this.allDefault))
{
// Overwrite all serialized fields, but not any nonserialized_*.
visitor.SetDefault(this);
return true;
}
JxlMatrix3x3F defaultInverse = JxlCms.DefaultInverseOpsinAbsorbanceMatrix();
for (int j = 0; j < 3; j++)
{
for (int i = 0; i < 3; i++)
{
if (!visitor.F16(defaultInverse[j][i], ref this.inverseMatrix[j][i]))
{
return false;
}
}
}
for (int i = 0; i < 3; i++)
{
if (!visitor.F16(JxlCms.NegOpsinAbsorbanceBiasRgb[i], ref this.opsinBiases[i]))
{
return false;
}
}
for (int i = 0; i < 4; i++)
{
if (!visitor.F16(DefaultQuantBias[i], ref this.quantBiases[i]))
{
return false;
}
}
return true;
}
} }

90
src/ImageSharp/Formats/Jxl/IO/Metadata/JxlToneMapping.cs

@ -7,15 +7,93 @@ namespace SixLabors.ImageSharp.Formats.Jxl.IO.Metadata;
internal sealed class JxlToneMapping : IJxlFields internal sealed class JxlToneMapping : IJxlFields
{ {
public bool AllDefault { get; set; } private bool allDefault;
private float intensityTarget;
private float lowerBoundIntensityLevel;
private bool relativeToMaxDisplay;
private float linearBelow;
private float minNits;
public float IntensityTarget { get; set; } public bool AllDefault
{
get => this.allDefault;
set => this.allDefault = value;
}
public float LowerBoundIntensityLevel { get; set; } public float IntensityTarget
{
get => this.intensityTarget;
set => this.intensityTarget = value;
}
public bool RelativeToMaxDisplay { get; set; } public float LowerBoundIntensityLevel
{
get => this.lowerBoundIntensityLevel;
set => this.lowerBoundIntensityLevel = value;
}
public float LinearBelow { get; set; } public bool RelativeToMaxDisplay
{
get => this.relativeToMaxDisplay;
set => this.relativeToMaxDisplay = value;
}
public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); public float LinearBelow
{
get => this.linearBelow;
set => this.linearBelow = value;
}
public float MinimumNits
{
get => this.minNits;
set => this.minNits = value;
}
public bool Visit(JxlVisitor visitor)
{
if (visitor.AllDefault(this, ref this.allDefault))
{
// Overwrite all serialized fields, but not any nonserialized_*.
visitor.SetDefault(this);
return true;
}
if (!visitor.F16(JxlFieldExpressions.Value(JxlConstants.DefaultIntensityTarget), ref this.intensityTarget))
{
return false;
}
if (this.intensityTarget <= 0F)
{
return false;
}
if (!visitor.F16(0F, ref this.minNits))
{
return false;
}
if (this.minNits < 0F || this.minNits > this.intensityTarget)
{
return false;
}
if (!visitor.Boolean(false, ref this.relativeToMaxDisplay))
{
return false;
}
if (!visitor.F16(0F, ref this.linearBelow))
{
return false;
}
if (this.linearBelow < 0F || (this.relativeToMaxDisplay && this.linearBelow > 1F))
{
return false;
}
return true;
}
} }

Loading…
Cancel
Save