From 79e8ce7b1367e2c8ae3cc2c3ed1cdddfafc2be2b Mon Sep 17 00:00:00 2001
From: winscripter <142818255+winscripter@users.noreply.github.com>
Date: Thu, 6 Aug 2026 13:15:07 +0400
Subject: [PATCH] Add Move to Front transform
See inverse_mtf-inl.h
---
.../Formats/Jxl/Processing/JxlInverseMtf.cs | 91 +++++++++++++++++++
1 file changed, 91 insertions(+)
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs
diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs
new file mode 100644
index 000000000..6638f8600
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/JxlInverseMtf.cs
@@ -0,0 +1,91 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
+
+///
+/// Inverse Move to Front implementation
+///
+internal static class JxlInverseMtf
+{
+ // NOTE: here we use Vector512 to store 64 bytes in a
+ // more efficient manner. However, it doesn't necessarily
+ // require 512-bit CPU vector support.
+ // If the user's CPU has 256-bit vectors, the JIT will emit
+ // such instructions for each half. Likewise, if the user's
+ // CPU only goes up to 128-bit vectors, the JIT will emit
+ // 128-bit vector code for each quarter. And if the CPU
+ // doesn't support SIMD at all, the JIT will emit scalar
+ // instructions.
+ public static void MoveToFront(Span v, byte index)
+ {
+ byte value = v[index];
+ byte i = index;
+
+ ref byte vR = ref MemoryMarshal.GetReference(v);
+
+ if (i < 4)
+ {
+ for (; i != 0; --i)
+ {
+ v[i] = v[i - 1];
+ }
+ }
+ else
+ {
+ int tail = i & 63;
+
+ if (tail != 0)
+ {
+ i -= (byte)tail;
+ Vector512 vec = Vector512.LoadUnsafe(ref Unsafe.Add(ref vR, i));
+ Vector512 prev = Vector512.LoadUnsafe(ref Unsafe.Add(ref vR, i + 1));
+
+ // TODO: optimize this?
+ Span maskBytes = stackalloc byte[64];
+
+ for (int j = 0; j < 64; j++)
+ {
+ maskBytes[j] = (byte)(j < tail ? 0xFF : 0);
+ }
+
+ Vector512 mask = Vector512.Create(maskBytes);
+ Vector512 filter = Vector512.ConditionalSelect(mask, vec, prev);
+ filter.StoreUnsafe(ref Unsafe.Add(ref vR, i + 1));
+ }
+
+ while (i != 0)
+ {
+ i -= 64;
+ Vector512 vec = Vector512.LoadUnsafe(ref Unsafe.Add(ref vR, i));
+ vec.StoreUnsafe(ref Unsafe.Add(ref vR, i + 1));
+ }
+ }
+
+ v[0] = value;
+ }
+
+ public static void InverseMoveToFrontTransform(Span v, int vLength)
+ {
+ Span mtf = stackalloc byte[256 + 64];
+ for (int i = 0; i < 256; i++)
+ {
+ mtf[i] = (byte)i;
+ }
+
+ for (int i = 0; i < vLength; i++)
+ {
+ byte index = v[i];
+ v[i] = mtf[index];
+
+ if (index != 0)
+ {
+ MoveToFront(mtf, index);
+ }
+ }
+ }
+}