mirror of https://github.com/SixLabors/ImageSharp
14 changed files with 1410 additions and 5 deletions
@ -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,39 @@ |
|||||
|
// 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 readonly int[][] JpegOrders = |
||||
|
[ |
||||
|
[0, 0, 0], // Grayscale
|
||||
|
[1, 0, 2], // Y'Cb'Cr
|
||||
|
[0, 1, 2], // None
|
||||
|
[0, 1, 2] // Anything else
|
||||
|
]; |
||||
|
|
||||
|
public static ReadOnlySpan<int> GetJpegOrder(JxlColorTransform transform, bool isGraysacle) |
||||
|
{ |
||||
|
if (isGraysacle) |
||||
|
{ |
||||
|
return JpegOrders[0]; |
||||
|
} |
||||
|
|
||||
|
if (transform == JxlColorTransform.YCbCr) |
||||
|
{ |
||||
|
return JpegOrders[1]; |
||||
|
} |
||||
|
else if (transform == JxlColorTransform.None) |
||||
|
{ |
||||
|
return JpegOrders[2]; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return JpegOrders[3]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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,743 @@ |
|||||
|
// 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.Metadata; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue