From 49d20566a366e1bbda1df9986ff8ffa784172b06 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 23:32:25 +1000 Subject: [PATCH] Use tensor addition for histogram offsets --- .../Common/Helpers/TensorPrimitives.cs | 12 +++++++++ .../Formats/Png/Filters/IPngFilterOperator.cs | 1 + .../Formats/Webp/Lossless/Vp8LHistogram.cs | 1 - .../HistogramEqualizationProcessor{TPixel}.cs | 6 ++--- .../Common/TensorPrimitivesTests.cs | 27 +++++++++++++++++++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives.cs index 7f97776ab..bcfd16bb9 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives.cs @@ -137,6 +137,18 @@ internal static class TensorPrimitives_ where T : IAdditionOperators, IAdditiveIdentity => InvokeSpanSpanIntoSpan>(x, y, destination); + /// + /// Computes the element-wise sum of the values in and the scalar . + /// + /// The element type. + /// The first addends. + /// The scalar second addend. + /// The destination for the sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(ReadOnlySpan x, T y, Span destination) + where T : IAdditionOperators, IAdditiveIdentity + => InvokeSpanScalarIntoSpan>(x, y, destination); + /// /// Computes the element-wise result of dividing the values in by . /// diff --git a/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs index c81187775..bae1ac477 100644 --- a/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs +++ b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Common.Helpers; diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs index c59235e70..eca60dd3f 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs @@ -533,7 +533,6 @@ internal abstract unsafe class Vp8LHistogram return cost; } - } internal sealed unsafe class OwnedVp8LHistogram : Vp8LHistogram, IDisposable diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs index fd01bd8dd..b624d5b0e 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Normalization; @@ -115,10 +116,7 @@ internal abstract class HistogramEqualizationProcessor : ImageProcessor< int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0; if (addToEachBin > 0) { - for (nuint i = 0; i < (uint)histogram.Length; i++) - { - Unsafe.Add(ref histogramBase, i) += addToEachBin; - } + TensorPrimitives_.Add(histogram, addToEachBin, histogram); } int residual = sumOverClip - (addToEachBin * this.LuminanceLevels); diff --git a/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs index 1cc5b943f..375fc000d 100644 --- a/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs @@ -90,6 +90,33 @@ public class TensorPrimitivesTests Assert.Equal(expected, x); } + /// + /// Verifies that scalar integer addition produces identical results for separate and in-place destinations. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void AddScalarInt32MatchesScalarFormula(int length) + { + int[] source = new int[length]; + int[] expected = new int[length]; + const int addend = 17; + + for (int i = 0; i < length; i++) + { + source[i] = (i * 37) - 200; + expected[i] = source[i] + addend; + } + + int[] destination = new int[length]; + TensorPrimitives_.Add(source, addend, destination); + Assert.Equal(expected, destination); + + int[] inPlace = (int[])source.Clone(); + TensorPrimitives_.Add(inPlace, addend, inPlace); + Assert.Equal(expected, inPlace); + } + /// /// Verifies that integer clamping produces identical results for separate and in-place destinations. ///