From 57392184374eb043c0342415380f3ec322795935 Mon Sep 17 00:00:00 2001 From: winscripter <142818255+winscripter@users.noreply.github.com> Date: Thu, 3 Sep 2026 21:23:22 +0400 Subject: [PATCH] Add a few CMS transfer functions (incomplete) --- .../JxlBt709TransferFunction.cs | 20 +++ .../JxlHybridLogGammaTransferFunction.cs | 53 ++++++++ .../JxlHybridLogGammaTransferFunctionBase.cs | 114 ++++++++++++++++++ ...ceptualQuantizationTransferFunctionBase.cs | 54 +++++++++ .../Formats/Jxl/Processing/JxlSimdUtils.cs | 8 ++ .../RenderPipeline/PatchDictionaryStage.cs | 1 - .../RenderPipeline/WriteToOutputStage.cs | 3 - 7 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlBt709TransferFunction.cs create mode 100644 src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlHybridLogGammaTransferFunction.cs create mode 100644 src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlHybridLogGammaTransferFunctionBase.cs create mode 100644 src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlPerceptualQuantizationTransferFunctionBase.cs diff --git a/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlBt709TransferFunction.cs b/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlBt709TransferFunction.cs new file mode 100644 index 0000000000..99b6548b60 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlBt709TransferFunction.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Cms.TransferFunctions; + +/// +/// ITU-R BT.709 transfer function +/// +internal static class JxlBt709TransferFunction +{ + public static double EncodedFromDisplay(double d) + { + if (d < Threshold) + { + return MulLow * d; + } + + return (MulHi * Math.Pow(d, PowHi)) + Sub; + } +} diff --git a/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlHybridLogGammaTransferFunction.cs b/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlHybridLogGammaTransferFunction.cs new file mode 100644 index 0000000000..3957c3ac7b --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlHybridLogGammaTransferFunction.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Formats.Jxl.Processing; + +namespace SixLabors.ImageSharp.Formats.Jxl.Cms.TransferFunctions; + +internal sealed class JxlHybridLogGammaTransferFunction : JxlHybridLogGammaTransferFunctionBase +{ + private const float HiAdd = B * Inverse12; + private const float HiMul = 0.003639807079052639f; // MathF.Exp(-C * RA) * Inverse12 + private const float HiPow = 8.067285659607931f; // RA * JxlMath.InverseLog2E + + /// + /// Initializes a new instance of the class. + /// + /// + /// Use static methods. Don't instantiate this class. + /// + private JxlHybridLogGammaTransferFunction() + { + } + + public static Vector EncodedFromDisplay(Vector x) + { + Vector sign = Vector.Create(0x80000000u).As(); + Vector originalSign = x & sign; + x = Vector.AndNot(sign, x); + Vector belowInverse12 = Vector.LessThan(x, Vector.Create(Inverse12)); + + Vector lo = Vector.SquareRoot(Vector.Create(3.0f) * x); + Vector hi = (Vector.Create(A * JxlMath.InverseLog2E) * Vector.Log2((Vector.Create(12f) * x) + Vector.Create(-B))) + Vector.Create(C); + Vector magnitude = Vector.ConditionalSelect(belowInverse12, lo, hi); + return Vector.AndNot(sign, magnitude) | originalSign; + } + + public static Vector DisplayFromEncoded(Vector x) + { + Vector sign = Vector.Create(0x80000000u).As(); + Vector originalSign = x & sign; + x = Vector.AndNot(sign, x); + Vector below05 = Vector.LessThan(x, Vector.Create(0.5f)); + + Vector lo = x * (x * Vector.Create(1f / 3f)); + Vector hi = (Pow2(x * Vector.Create(HiPow)) * Vector.Create(HiMul)) + Vector.Create(HiAdd); + Vector magnitude = Vector.ConditionalSelect(below05, lo, hi); + + return Vector.AndNot(sign, magnitude) | originalSign; + } + + private static Vector Pow2(Vector x) => x * x; +} diff --git a/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlHybridLogGammaTransferFunctionBase.cs b/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlHybridLogGammaTransferFunctionBase.cs new file mode 100644 index 0000000000..795db106c3 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlHybridLogGammaTransferFunctionBase.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Cms.TransferFunctions; + +/// +/// Base class for HLG transfer function. +/// +internal abstract class JxlHybridLogGammaTransferFunctionBase +{ + // Shared constants used by transfer functions + protected const float A = 0.17883277f; + protected const float RA = 1.0f / A; + protected const float B = 1 - (4 * A); + protected const float C = 0.5599107295f; + protected const float Inverse12 = 1.0f / 12.0f; + + /// + /// Converts encoded signal to display signal. + /// + /// The encoded signal + /// The display signal + protected static double DisplayFromEncoded(double encoded) => Ootf(InverseOotf(encoded)); + + /// + /// Converts display signal to encoded signal. + /// + /// The display signal + /// The encoded signal + protected static double EncodedFromDisplay(double display) => Oetf(InverseOetf(display)); + + /// + /// Opto-Electronic Transfer Function - converts + /// real-world scene light (s) into a digital video + /// signal inside a camera. + /// + /// + /// + /// + /// Scene light + /// Digital video signal + private static double Oetf(double s) + { + if (s == 0) + { + return 0; + } + + double originalSign = s; + + s = Math.Abs(s); + + if (s <= Inverse12) + { + return Math.CopySign(Math.Sqrt(3.0 * s), originalSign); + } + + double e = (A * Math.Log((12 * s) - B)) + C; + DebugGuard.MustBeGreaterThan(e, 0.0, nameof(e)); + + return Math.CopySign(e, originalSign); + } + + /// + /// Inverse Opto-Electronic Transfer Function - converts + /// digital video signal into a real-world scene light. + /// + /// + /// + /// + /// Digital video signal + /// Scene light + private static double InverseOetf(double e) + { + if (e == 0) + { + return 0; + } + + double originalSign = e; + + e = Math.Abs(e); + + if (e <= 0.5) + { + return Math.CopySign(e * e * (1.0 / 3), originalSign); + } + + double s = (Math.Exp((e - C) * RA) + B) * Inverse12; + DebugGuard.MustBeGreaterThan(s, 0.0, nameof(s)); + + return Math.CopySign(s, originalSign); + } + + /// + /// Opto-Optical Transfer Function - as-is. + /// + /// + /// + /// + /// Input signal + /// Digital video signal + private static double Ootf(double s) => s; + + /// + /// Inverse Opto-Optical Transfer Function - as-is. + /// + /// + /// + /// + /// Digital video signal + /// Scene light + private static double InverseOotf(double s) => s; +} diff --git a/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlPerceptualQuantizationTransferFunctionBase.cs b/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlPerceptualQuantizationTransferFunctionBase.cs new file mode 100644 index 0000000000..eea7b06560 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Cms/TransferFunctions/JxlPerceptualQuantizationTransferFunctionBase.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Cms.TransferFunctions; + +/// +/// Base class for PQ transfer function. +/// +internal abstract class JxlPerceptualQuantizationTransferFunctionBase +{ + private const double M1 = 2610.0 / 16384; + private const double M2 = (2523.0 / 4096) * 128; + private const double C1 = 3424.0 / 4096; + private const double C2 = (2413.0 / 4096) * 32; + private const double C3 = (2392.0 / 4096) * 32; + + protected static double DisplayFromEncoded(float displayIntensityTarget, double e) + { + if (e == 0.0) + { + return 0.0; + } + + double originalSign = e; + + e = Math.Abs(e); + + double xp = Math.Pow(e, 1.0 / M2); + double num = Math.Max(xp - C1, 0.0); + double den = C2 - (C3 * xp); + double d = Math.Pow(num / den, 1.0 / M1); + + return Math.CopySign(d * (10000.0 / displayIntensityTarget), originalSign); + } + + protected static double EncodedFromDisplay(float displayIntensityTarget, double d) + { + if (d == 0.0) + { + return 0.0; + } + + double originalSign = d; + + d = Math.Abs(d); + + double xp = Math.Pow(d * (displayIntensityTarget * (1 / 10000)), M1); + double num = C1 + (xp * C2); + double den = 1.0 + (xp * C3); + double e = Math.Pow(num / den, M2); + + return Math.CopySign(e, originalSign); + } +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.cs index dd8e7314ee..8da98bbf4f 100644 --- a/src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.cs +++ b/src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -101,4 +102,11 @@ internal static partial class JxlSimdUtils } } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector Pow(Vector @base, Vector exponent) + { + Vector vec = Vector.Log2(@base) * exponent; + return vec * vec; + } } diff --git a/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/PatchDictionaryStage.cs b/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/PatchDictionaryStage.cs index 0e6bf82745..f2f2f0054f 100644 --- a/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/PatchDictionaryStage.cs +++ b/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/PatchDictionaryStage.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Drawing; using SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; using SixLabors.ImageSharp.Memory; diff --git a/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/WriteToOutputStage.cs b/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/WriteToOutputStage.cs index ad790ed10a..d442faeac6 100644 --- a/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/WriteToOutputStage.cs +++ b/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/WriteToOutputStage.cs @@ -1,10 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Numerics; using SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; -using SixLabors.ImageSharp.Formats.Jxl.Processing.Image; -using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline;