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