diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlAcStrategy.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlAcStrategy.cs index 84b246259..32c7fe3e1 100644 --- a/src/ImageSharp/Formats/Jxl/Processing/JxlAcStrategy.cs +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlAcStrategy.cs @@ -4,7 +4,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using static SixLabors.ImageSharp.Formats.Jxl.JxlFrameDimensions; +using static SixLabors.ImageSharp.Formats.Jxl.Processing.JxlFrameDimensions; #pragma warning disable SA1405 // Debug.Assert should provide message text diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlChromaFromLuma.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlChromaFromLuma.cs new file mode 100644 index 000000000..012114047 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlChromaFromLuma.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Jxl.Fields; +using static SixLabors.ImageSharp.Formats.Jxl.Processing.JxlFrameDimensions; + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing; + +/// +/// Shared constants for CFL (Chroma From Luma) functions. +/// +internal static class JxlChromaFromLuma +{ + /// + /// Tiles are 64x64. + /// + public const int ColorTileDimension = 64; + + /// + /// Division of the color tile dimension by the block dimension. Therefore, + /// this is 8x8. + /// + public const int ColorTileDimensionInBlocks = ColorTileDimension / BlockDimensions; + + public const int DefaultColorFactor = 84; + + /// + /// Chroma From Luma fixed point precision, which is + /// 11 bits. + /// + public const int CflFixedPointPrecision = 11; + + /// + /// 524287 + /// + public const int CflFixedPointRatioMax = (256 << CflFixedPointPrecision) - 1; + + /// + /// Shared variable U32 distributions for the color factor. + /// + public static readonly JxlU32Enc ColorFactorDistribution = new( + JxlFieldExpressions.Value(DefaultColorFactor), + JxlFieldExpressions.Value(256), + JxlFieldExpressions.BitsOffset(8, 2), + JxlFieldExpressions.BitsOffset(16, 258)); +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlColorCorrelation.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlColorCorrelation.cs new file mode 100644 index 000000000..69e617ea9 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlColorCorrelation.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Jxl.Fields; +using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing; + +internal sealed class JxlColorCorrelation +{ + private float baseCorrelationX; + private float baseCorrelationB = DefaultYToBRatio; + + private readonly float[] dcFactors = new float[4]; + private uint colorFactor = JxlChromaFromLuma.DefaultColorFactor; + private float colorScale = 1.0f / JxlChromaFromLuma.DefaultColorFactor; + + public int YToXDc { get; private set; } + + public int YToBDc { get; private set; } + + public float ColorFactor => this.colorFactor; + + public float BaseCorrelationX + { + get => this.baseCorrelationX; + set => this.baseCorrelationX = value; + } + + public float BaseCorrelationB + { + get => this.baseCorrelationB; + set => this.baseCorrelationB = value; + } + + public bool IsJpegCompatible => + this.BaseCorrelationX == 0 && + this.BaseCorrelationB == 0 && + this.YToBDc == 0 && + this.YToXDc == 0 && + this.ColorFactor == JxlChromaFromLuma.DefaultColorFactor; + + public ReadOnlySpan DcFactors => this.dcFactors; + + public float YToXRatio(int xFactor) => this.BaseCorrelationX + (xFactor * this.colorScale); + + public float YToBRatio(int bFactor) => this.BaseCorrelationB + (bFactor * this.colorScale); + + public void SetColorFactor(uint factor) + { + this.colorFactor = factor; + this.colorScale = 1f / factor; + this.RecomputeDcFactors(); + } + + public void SetYToBDc(int yToBDc) + { + this.YToBDc = yToBDc; + this.RecomputeDcFactors(); + } + + public void SetYToXDc(int yToXDc) + { + this.YToXDc = yToXDc; + this.RecomputeDcFactors(); + } + + public void RecomputeDcFactors() + { + this.dcFactors[0] = this.YToXRatio(this.YToXDc); + this.dcFactors[2] = this.YToBRatio(this.YToBDc); + } + + public bool DecodeDc(JxlBitReader reader) + { + bool allDefault = reader.ReadBoolean(); + + if (allDefault) + { + return true; + } + + this.SetColorFactor(JxlU32Coder.Read(JxlChromaFromLuma.ColorFactorDistribution, reader)); + + if (!JxlF16Coder.Read(reader, ref this.baseCorrelationX)) + { + return false; + } + + if (MathF.Abs(this.baseCorrelationX) > 4f) + { + throw new InvalidOperationException("Base X correlation is out of range"); + } + + if (!JxlF16Coder.Read(reader, ref this.baseCorrelationB)) + { + return false; + } + + if (MathF.Abs(this.baseCorrelationB) > 4f) + { + throw new InvalidOperationException("Base B correlation is out of range"); + } + + this.YToXDc = (int)reader.ReadBits32(8) + sbyte.MinValue; + this.YToBDc = (int)reader.ReadBits32(8) + sbyte.MinValue; + + this.RecomputeDcFactors(); + return true; + } + + public static int RatioJpeg(int factor) => factor * (1 << JxlChromaFromLuma.CflFixedPointPrecision) / JxlChromaFromLuma.DefaultColorFactor; +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlColorCorrelationMap.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlColorCorrelationMap.cs new file mode 100644 index 000000000..89ae3f729 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlColorCorrelationMap.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; +using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing; + +/// +/// JPEG XL Color correlation map. +/// +internal sealed class JxlColorCorrelationMap +{ + public JxlColorCorrelation Base { get; set; } = new(); + + public JxlImageSB? YToXMap { get; set; } + + public JxlImageSB? YToBMap { get; set; } + + public bool DecodeDc(JxlBitReader reader) => this.Base.DecodeDc(reader); + + public static JxlColorCorrelationMap Create(Configuration configuration, int width, int height, bool xyb) + { + JxlColorCorrelationMap map = new(); + + (int xBlocks, int yBlocks) = (DivCeil(width, JxlChromaFromLuma.ColorTileDimension), DivCeil(height, JxlChromaFromLuma.ColorTileDimension)); + + map.YToXMap = new JxlImageSB(configuration, xBlocks, yBlocks); + map.YToBMap = new JxlImageSB(configuration, xBlocks, yBlocks); + + ZeroFillImage(map.YToXMap); + ZeroFillImage(map.YToBMap); + + if (!xyb) + { + map.Base.BaseCorrelationB = 0; + } + + map.Base.RecomputeDcFactors(); + return map; + } +}