From 093d84612c2901eb95a1043b700341281d8bed51 Mon Sep 17 00:00:00 2001 From: winscripter <142818255+winscripter@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:40:59 +0400 Subject: [PATCH] Finish inverse RCT --- .../Processing/Modular/Transforms/JxlRct.cs | 83 ++++++++++++++++++- .../Modular/Transforms/JxlTransform.cs | 28 +++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlRct.cs b/src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlRct.cs index 35b865661..ab55b1297 100644 --- a/src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlRct.cs +++ b/src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlRct.cs @@ -10,10 +10,22 @@ namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Transforms; /// internal static class JxlRct { - public static void InverseRctRow(int transformType, Span in0, Span in1, Span in2, Span out0, Span out1, Span out2, int width) + /// + /// Performs Inverse Reversible Color Transform (RCT) on one row. + /// + /// The kind of RCT. + /// Input Y + /// Input Co + /// Input Cg + /// Output R + /// Output G + /// Output B + private static void InverseRctRow(int transformType, Span in0, Span in1, Span in2, Span out0, Span out1, Span out2) { DebugGuard.MustBeBetweenOrEqualTo(transformType, 0, 6, nameof(transformType)); + int width = in0.Length; // All input & output channels have equal widths + int second = transformType >> 1; int third = transformType & 1; @@ -112,4 +124,73 @@ internal static class JxlRct } } } + + /// + /// Performs Inverse Reversible Color Transform (RCT) on an entire + /// image. + /// + /// + /// The configuration is used to access maximum degree of parallelism. + /// + /// + /// Image to compute inverse RCT. + /// + /// + /// Offset of the color channel for Y, Co, Cg. + /// + /// + /// Type of Reversible Color Transform + /// + /// + /// Invoked when RCT/permutation is invalid. + /// + public static void InverseRct(Configuration configuration, JxlModularImage img, int beginC, int rctType) + { + JxlTransform.CheckEqualChannels(img, beginC, beginC + 2); + + int m = beginC; + JxlModularChannel c0 = img.Channels[m + 0]; + int w = c0.Width; + int h = c0.Height; + + if (rctType == 0) + { + // No-op + return; + } + + int permutation = rctType / 7; + + if (permutation >= 7) + { + throw new InvalidOperationException("Permutation must be <= 6"); + } + + int custom = rctType % 7; + + if (custom == 0) + { + // Permute-only. + JxlModularChannel ch0 = img.Channels[m]; + JxlModularChannel ch1 = img.Channels[m + 1]; + JxlModularChannel ch2 = img.Channels[m + 2]; + img.Channels[m + (permutation % 3)] = ch0; + img.Channels[m + ((permutation + 1 + (permutation / 3)) % 3)] = ch1; + img.Channels[m + ((permutation + 2 - (permutation / 3)) % 3)] = ch2; + return; + } + + _ = Parallel.For(0, configuration.MaxDegreeOfParallelism, y => + { + Span in0 = img.Channels[m].GetRow(y); + Span in1 = img.Channels[m + 1].GetRow(y); + Span in2 = img.Channels[m + 2].GetRow(y); + + Span out0 = img.Channels[m + (permutation % 3)].GetRow(y); + Span out1 = img.Channels[m + ((permutation + 1 + (permutation / 3)) % 3)].GetRow(y); + Span out2 = img.Channels[m + ((permutation + 2 - (permutation / 3)) % 3)].GetRow(y); + + InverseRctRow(custom, in0, in1, in2, out0, out1, out2); + }); + } } diff --git a/src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlTransform.cs b/src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlTransform.cs index 780301061..860adb469 100644 --- a/src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlTransform.cs +++ b/src/ImageSharp/Formats/Jxl/Processing/Modular/Transforms/JxlTransform.cs @@ -8,4 +8,32 @@ namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Transforms; internal sealed class JxlTransform : IJxlFields { public bool Visit(JxlVisitor visitor) => throw new NotImplementedException(); + + public static void CheckEqualChannels(JxlModularImage image, int c1, int c2) + { + int channelsCount = image.Channels.Count; + + if (c1 > channelsCount || c2 >= channelsCount || c2 < c1) + { + throw new InvalidOperationException($"Invalid channel range: {c1}..{c2} (there are only {channelsCount} channels)"); + } + + if (c1 < image.NbMetaChannels && c2 >= image.NbMetaChannels) + { + throw new InvalidOperationException("Invalid: transforming mix of meta and nonmeta"); + } + + JxlModularChannel ch1 = image.Channels[c1]; + for (int c = c1 + 1; c <= c2; c++) + { + JxlModularChannel ch2 = image.Channels[c]; + if (ch1.Width != ch2.Width || + ch1.Height != ch2.Height || + ch1.HorizontalShift != ch2.HorizontalShift || + ch1.VerticalShift != ch2.VerticalShift) + { + throw new InvalidOperationException($"Channel {c} is not equal"); + } + } + } }