From b0f0d34a2eb14ba3b0fa2a4fda8c270fe9ea3e19 Mon Sep 17 00:00:00 2001 From: winscripter <142818255+winscripter@users.noreply.github.com> Date: Sat, 5 Sep 2026 00:35:43 +0400 Subject: [PATCH] Complete Butteraugli and its tests --- .../Processing/Image/JxlImageOperations.cs | 35 +++++ .../Jxl/Processing/ButteraugliTests.cs | 139 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 tests/ImageSharp.Tests/Formats/Jxl/Processing/ButteraugliTests.cs diff --git a/src/ImageSharp/Formats/Jxl/Processing/Image/JxlImageOperations.cs b/src/ImageSharp/Formats/Jxl/Processing/Image/JxlImageOperations.cs index b4e98c5ca4..af665db380 100644 --- a/src/ImageSharp/Formats/Jxl/Processing/Image/JxlImageOperations.cs +++ b/src/ImageSharp/Formats/Jxl/Processing/Image/JxlImageOperations.cs @@ -64,6 +64,41 @@ internal static class JxlImageOperations return true; } + /// + /// Copies everything from one image to another. + /// + /// The type of image to copy. + /// Source image (read-only) + /// Destination image (write-only) + /// + /// Status of the copy operation + /// + public static bool CopyImage(JxlImage3 from, JxlImage3 to) + where T : unmanaged + { + if (!SameSize(from, to)) + { + return false; + } + + if (from.XSize == 0 || from.YSize == 0) + { + return true; + } + + for (int c = 0; c < 3; c++) + { + for (int y = 0; y < from.YSize; y++) + { + Span rowFrom = from.PlaneRow(c, y); + Span rowTo = to.PlaneRow(c, y); + rowFrom.CopyTo(rowTo); + } + } + + return true; + } + /// /// Within bounds specified by input rectangles, copies everything from one plane to another. /// diff --git a/tests/ImageSharp.Tests/Formats/Jxl/Processing/ButteraugliTests.cs b/tests/ImageSharp.Tests/Formats/Jxl/Processing/ButteraugliTests.cs new file mode 100644 index 0000000000..8adabd83e7 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jxl/Processing/ButteraugliTests.cs @@ -0,0 +1,139 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using Microsoft.Diagnostics.Runtime.Interop; +using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; +using SixLabors.ImageSharp.Formats.Jxl.Processing.Butteraugli; +using SixLabors.ImageSharp.Formats.Jxl.Processing.Image; + +namespace SixLabors.ImageSharp.Tests.Formats.Jxl.Processing; + +/// +/// Tests for Google Butteraugli (C# implementation), a tool +/// used for comparing images in a more sophisticated way +/// (the way humans may notice differences). For example, +/// if every pixel was off by 1, PSNR, which operates on a +/// pixel-by-pixel basis, would report a large difference, +/// while Butteraugli would report barely any differences, +/// as us humans wouldn't notice anything if every pixel +/// was just off by one. +/// +public class ButteraugliTests +{ + private static JxlImage3F SinglePixelImage(float r, float g, float b) + { + JxlImage3F img = new(TestEnvironment.Configuration, 1, 1); + img.PlaneRow(0, 0)[0] = r; + img.PlaneRow(1, 0)[0] = r; + img.PlaneRow(2, 0)[0] = r; + return img; + } + + private static void AddUniformNoise(JxlImage3F img, float d, ulong seed) + { + Rng generator = new(seed); + + for (int y = 0; y < img.YSize; ++y) + { + for (int c = 0; c < 3; ++c) + { + Span planeRow = img.PlaneRow(c, y); + + for (int x = 0; x < img.XSize; ++x) + { + planeRow[x] += generator.UniformF(-d, d); + } + } + } + } + + private static void AddEdge(JxlImage3F image, float d, int x0, int y0) + { + int h = Math.Min(image.YSize - y0, 100); + int w = Math.Min(image.XSize - x0, 5); + + for (int dy = 0; dy < h; ++dy) + { + Span planeRow = image.PlaneRow(1, y0 + dy); + + for (int dx = 0; dx < w; ++dx) + { + planeRow[x0 + dx] += d; + } + } + } + + [Fact] + public void TestSinglePixel() + { + JxlImage3F rgb0 = SinglePixelImage(0.5f, 0.5f, 0.5f); + JxlImage3F rgb1 = SinglePixelImage(0.5f, 0.49f, 0.5f); + + ButteraugliParameters butteraugliParameters = new(); + JxlImageF diffmap = new(); + + Assert.True( + Butteraugli.ButteraugliInterface(TestEnvironment.Configuration, rgb0, rgb1, butteraugliParameters, diffmap, out double diffval), + "Butteraugli initialization failed"); + + Assert.True(new TolerantMath(0.5).AreEqual(diffval, 2.5), $"Diff value isn't even close to 2.5 (it's {diffval})"); + + JxlImageF diffmap2 = new(); + Assert.True(Butteraugli.ButteraugliInterfaceInPlace( + TestEnvironment.Configuration, + rgb0, + rgb1, + butteraugliParameters, + diffmap2, + out double diffval2)); + + Assert.True(new TolerantMath(1e-10).AreEqual(diffval, diffval2), $"Diff value isn't even close to diffval2 (diffval={diffval}, diffval2={diffval2})"); + } + + // TODO: we need to port test image stuff from libjxl + [Fact] + public void TestLargeImage() + { + const int xSize = 1024; + const int ySize = 1024; + + JxlTestImage img = new(); + img.SetDimensions(xSize, ySize); + + JxlTestFrame frame = img.AddFrame(); + frame.RandomFill(777); + + JxlImage3F rgb0 = GetColorImage(img.Ppf); + JxlImage3F rgb1 = new(TestEnvironment.Configuration, xSize, ySize); + JxlImageOperations.CopyImage(rgb0, rgb1); + + AddUniformNoise(rgb1, 0.02f, 7777uL); + AddEdge(rgb1, 0.1f, xSize / 2, xSize / 2); + + ButteraugliParameters butteraugliParameters = new(); + JxlImageF diffmap = new(); + Assert.True( + Butteraugli.ButteraugliInterface(TestEnvironment.Configuration,, rgb0, rgb1, butteraugliParameters, diffmap, out double diffval), + "Couldn't initialize Butteraugli"); + + double distp = Butteraugli.ComputeDistanceP(diffmap, butteraugliParameters, 3.0); + Assert.True(new TolerantMath(0.5).AreEqual(diffval, 4.0), $"Diff isn't even close to 4.0 (diffval={diffval})"); + Assert.True(new TolerantMath(0.5).AreEqual(distp, 1.5), $"Distance isn't even close to 4.0 (distp={distp})"); + + JxlImageF diffmap2 = new(); + Assert.True( + Butteraugli.ButteraugliInterfaceInPlace( + TestEnvironment.Configuration, + rgb0, + rgb1, + butteraugliParameters, + diffmap2, + out double diffval2), + "Butteraugli in-place interface initialization failed"); + + double distp2 = Butteraugli.ComputeDistanceP(diffmap2, butteraugliParameters, 3.0); + + Assert.True(new TolerantMath(5e-7).AreEqual(diffval, diffval2), $"Diffval != diffval2 (diffval={diffval}, diffval2={diffval2})"); + Assert.True(new TolerantMath(1e-7).AreEqual(distp, distp2), $"Distp != distp2 (distp={distp}, distp2={distp2})"); + } +}