diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
index 57c202918..942a80ab7 100644
--- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
@@ -140,98 +140,12 @@ internal static class AverageFilter
/// The sum of the total variance of the filtered row.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, uint bytesPerPixel, out int sum)
- {
- DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
- DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
-
- ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
- ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
- ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
- sum = 0;
-
- // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
- resultBaseRef = (byte)FilterType.Average;
-
- nuint x = 0;
- for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */)
- {
- byte scan = Unsafe.Add(ref scanBaseRef, x);
- byte above = Unsafe.Add(ref prevBaseRef, x);
- ++x;
- ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
- res = (byte)(scan - (above >> 1));
- sum += Numerics.Abs(unchecked((sbyte)res));
- }
-
- if (Avx2.IsSupported)
- {
- Vector256 zero = Vector256.Zero;
- Vector256 sumAccumulator = Vector256.Zero;
- Vector256 allBitsSet = Avx2.CompareEqual(sumAccumulator, sumAccumulator).AsByte();
-
- for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count)
- {
- Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
- Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft));
- Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x));
-
- Vector256 avg = Avx2.Xor(Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), allBitsSet);
- Vector256 res = Avx2.Subtract(scan, avg);
-
- Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
- x += (uint)Vector256.Count;
-
- sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
- }
-
- sum += Numerics.EvenReduceSum(sumAccumulator);
- }
- else if (Sse2.IsSupported)
- {
- Vector128 zero = Vector128.Zero;
- Vector128 sumAccumulator = Vector128.Zero;
- Vector128 allBitsSet = Sse2.CompareEqual(sumAccumulator, sumAccumulator).AsByte();
-
- for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector128.Count; xLeft += (uint)Vector128.Count)
- {
- Vector128 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
- Vector128 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft));
- Vector128 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x));
-
- Vector128 avg = Sse2.Xor(Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), allBitsSet);
- Vector128 res = Sse2.Subtract(scan, avg);
-
- Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
- x += (uint)Vector128.Count;
-
- Vector128 absRes;
- if (Ssse3.IsSupported)
- {
- absRes = Ssse3.Abs(res.AsSByte());
- }
- else
- {
- Vector128 mask = Sse2.CompareGreaterThan(zero.AsSByte(), res.AsSByte());
- absRes = Sse2.Xor(Sse2.Add(res.AsSByte(), mask), mask).AsByte();
- }
-
- sumAccumulator = Sse2.Add(sumAccumulator, Sse2.SumAbsoluteDifferences(absRes, zero).AsInt32());
- }
-
- sum += Numerics.EvenReduceSum(sumAccumulator);
- }
-
- for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
- {
- byte scan = Unsafe.Add(ref scanBaseRef, x);
- byte left = Unsafe.Add(ref scanBaseRef, xLeft);
- byte above = Unsafe.Add(ref prevBaseRef, x);
- ++x;
- ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
- res = (byte)(scan - Average(left, above));
- sum += Numerics.Abs(unchecked((sbyte)res));
- }
- }
+ => PngFilterEncoder.Encode(
+ scanline,
+ previousScanline,
+ result,
+ bytesPerPixel,
+ out sum);
///
/// Calculates the average value of two bytes
diff --git a/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs
new file mode 100644
index 000000000..1113a5df6
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs
@@ -0,0 +1,524 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.Arm;
+using System.Runtime.Intrinsics.X86;
+
+namespace SixLabors.ImageSharp.Formats.Png.Filters;
+
+///
+/// Defines the scalar and SIMD mappings used by the shared PNG filter traversal.
+///
+internal interface IPngFilterOperator
+{
+ ///
+ /// Gets the filter type written to the leading result byte.
+ ///
+ static abstract FilterType Type { get; }
+
+ ///
+ /// Gets a value indicating whether the predictor reads the left component.
+ ///
+ static abstract bool UsesLeft { get; }
+
+ ///
+ /// Gets a value indicating whether the predictor reads the above component.
+ ///
+ static abstract bool UsesAbove { get; }
+
+ ///
+ /// Gets a value indicating whether the predictor reads the upper-left component.
+ ///
+ static abstract bool UsesUpperLeft { get; }
+
+ ///
+ /// Filters one byte from its PNG neighborhood.
+ ///
+ /// The component being filtered.
+ /// The corresponding component in the preceding pixel.
+ /// The corresponding component in the preceding scanline.
+ /// The preceding component in the preceding scanline.
+ /// The filtered residual.
+ static abstract byte Invoke(byte scan, byte left, byte above, byte upperLeft);
+
+ ///
+ /// Filters sixteen byte lanes from their PNG neighborhoods.
+ ///
+ /// The components being filtered.
+ /// The corresponding components in the preceding pixels.
+ /// The corresponding components in the preceding scanline.
+ /// The preceding components in the preceding scanline.
+ /// The filtered residuals.
+ static abstract Vector128 Invoke(
+ Vector128 scan,
+ Vector128 left,
+ Vector128 above,
+ Vector128 upperLeft);
+
+ ///
+ /// Filters thirty-two byte lanes from their PNG neighborhoods.
+ ///
+ /// The components being filtered.
+ /// The corresponding components in the preceding pixels.
+ /// The corresponding components in the preceding scanline.
+ /// The preceding components in the preceding scanline.
+ /// The filtered residuals.
+ static abstract Vector256 Invoke(
+ Vector256 scan,
+ Vector256 left,
+ Vector256 above,
+ Vector256 upperLeft);
+
+ ///
+ /// Filters sixty-four byte lanes from their PNG neighborhoods.
+ ///
+ /// The components being filtered.
+ /// The corresponding components in the preceding pixels.
+ /// The corresponding components in the preceding scanline.
+ /// The preceding components in the preceding scanline.
+ /// The filtered residuals.
+ static abstract Vector512 Invoke(
+ Vector512 scan,
+ Vector512 left,
+ Vector512 above,
+ Vector512 upperLeft);
+}
+
+///
+/// Maps each component to its difference from the corresponding component in the preceding pixel.
+///
+internal readonly struct SubFilterOperator : IPngFilterOperator
+{
+ ///
+ public static FilterType Type => FilterType.Sub;
+
+ ///
+ public static bool UsesLeft => true;
+
+ ///
+ public static bool UsesAbove => false;
+
+ ///
+ public static bool UsesUpperLeft => false;
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) => (byte)(scan - left);
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector128 Invoke(
+ Vector128 scan,
+ Vector128 left,
+ Vector128 above,
+ Vector128 upperLeft)
+ => scan - left;
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector256 Invoke(
+ Vector256 scan,
+ Vector256 left,
+ Vector256 above,
+ Vector256 upperLeft)
+ => scan - left;
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector512 Invoke(
+ Vector512 scan,
+ Vector512 left,
+ Vector512 above,
+ Vector512 upperLeft)
+ => scan - left;
+}
+
+///
+/// Maps each component to its difference from the component directly above it.
+///
+internal readonly struct UpFilterOperator : IPngFilterOperator
+{
+ ///
+ public static FilterType Type => FilterType.Up;
+
+ ///
+ public static bool UsesLeft => false;
+
+ ///
+ public static bool UsesAbove => true;
+
+ ///
+ public static bool UsesUpperLeft => false;
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) => (byte)(scan - above);
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector128 Invoke(
+ Vector128 scan,
+ Vector128 left,
+ Vector128 above,
+ Vector128 upperLeft)
+ => scan - above;
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector256 Invoke(
+ Vector256 scan,
+ Vector256 left,
+ Vector256 above,
+ Vector256 upperLeft)
+ => scan - above;
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector512 Invoke(
+ Vector512 scan,
+ Vector512 left,
+ Vector512 above,
+ Vector512 upperLeft)
+ => scan - above;
+}
+
+///
+/// Maps each component to its difference from the truncated average of its left and above neighbors.
+///
+internal readonly struct AverageFilterOperator : IPngFilterOperator
+{
+ ///
+ public static FilterType Type => FilterType.Average;
+
+ ///
+ public static bool UsesLeft => true;
+
+ ///
+ public static bool UsesAbove => true;
+
+ ///
+ public static bool UsesUpperLeft => false;
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static byte Invoke(byte scan, byte left, byte above, byte upperLeft)
+ => (byte)(scan - ((left + above) >> 1));
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector128 Invoke(
+ Vector128 scan,
+ Vector128 left,
+ Vector128 above,
+ Vector128 upperLeft)
+ {
+ Vector128 average;
+
+ if (Sse2.IsSupported)
+ {
+ // PAVG rounds upward. Complementing both inputs and the result converts
+ // that rounding into the floor((left + above) / 2) required by PNG.
+ average = ~Sse2.Average(~left, ~above);
+ }
+ else if (AdvSimd.IsSupported)
+ {
+ // ARM's halving add truncates directly and therefore needs no correction.
+ average = AdvSimd.FusedAddHalving(left, above);
+ }
+ else
+ {
+ // Portable 128-bit backends use the carry-free average identity. Shared
+ // bits supply the integer part while differing bits supply half the remainder.
+ average = (left & above) + Vector128.ShiftRightLogical(left ^ above, 1);
+ }
+
+ return scan - average;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector256 Invoke(
+ Vector256 scan,
+ Vector256 left,
+ Vector256 above,
+ Vector256 upperLeft)
+ => scan - ~Avx2.Average(~left, ~above);
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector512 Invoke(
+ Vector512 scan,
+ Vector512 left,
+ Vector512 above,
+ Vector512 upperLeft)
+ => scan - ~Avx512BW.Average(~left, ~above);
+}
+
+///
+/// Maps each component to its difference from the nearest Paeth neighbor.
+///
+internal readonly struct PaethFilterOperator : IPngFilterOperator
+{
+ ///
+ public static FilterType Type => FilterType.Paeth;
+
+ ///
+ public static bool UsesLeft => true;
+
+ ///
+ public static bool UsesAbove => true;
+
+ ///
+ public static bool UsesUpperLeft => true;
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static byte Invoke(byte scan, byte left, byte above, byte upperLeft)
+ {
+ int p = left + above - upperLeft;
+ int distanceLeft = Numerics.Abs(p - left);
+ int distanceAbove = Numerics.Abs(p - above);
+ int distanceUpperLeft = Numerics.Abs(p - upperLeft);
+
+ // PNG resolves equal distances in left, above, upper-left order.
+ byte predictor = distanceLeft <= distanceAbove && distanceLeft <= distanceUpperLeft
+ ? left
+ : distanceAbove <= distanceUpperLeft ? above : upperLeft;
+
+ return (byte)(scan - predictor);
+ }
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector128 Invoke(
+ Vector128 scan,
+ Vector128 left,
+ Vector128 above,
+ Vector128 upperLeft)
+ {
+ Vector128 predictor = Predict(left, above, upperLeft);
+ return scan - predictor;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector256 Invoke(
+ Vector256 scan,
+ Vector256 left,
+ Vector256 above,
+ Vector256 upperLeft)
+ {
+ Vector256 predictor = Predict(left, above, upperLeft);
+ return scan - predictor;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ public static Vector512 Invoke(
+ Vector512 scan,
+ Vector512 left,
+ Vector512 above,
+ Vector512 upperLeft)
+ {
+ Vector512 predictor = Predict(left, above, upperLeft);
+ return scan - predictor;
+ }
+
+ ///
+ /// Selects the nearest Paeth neighbor for sixteen independent byte lanes.
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ private static Vector128 Predict(
+ Vector128 left,
+ Vector128 above,
+ Vector128 upperLeft)
+ {
+ Vector128 aboveMinusUpper = SubtractSaturate(above, upperLeft);
+ Vector128 leftMinusUpper = SubtractSaturate(left, upperLeft);
+ Vector128 distanceLeft = SubtractSaturate(upperLeft, above) | aboveMinusUpper;
+ Vector128 distanceAbove = SubtractSaturate(upperLeft, left) | leftMinusUpper;
+
+ return SelectPredictor(
+ left,
+ above,
+ upperLeft,
+ aboveMinusUpper,
+ leftMinusUpper,
+ distanceLeft,
+ distanceAbove);
+ }
+
+ ///
+ /// Selects the nearest Paeth neighbor for thirty-two independent byte lanes.
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ private static Vector256 Predict(
+ Vector256 left,
+ Vector256 above,
+ Vector256 upperLeft)
+ {
+ Vector256 aboveMinusUpper = Avx2.SubtractSaturate(above, upperLeft);
+ Vector256 leftMinusUpper = Avx2.SubtractSaturate(left, upperLeft);
+ Vector256 distanceLeft = Avx2.SubtractSaturate(upperLeft, above) | aboveMinusUpper;
+ Vector256 distanceAbove = Avx2.SubtractSaturate(upperLeft, left) | leftMinusUpper;
+
+ return SelectPredictor(
+ left,
+ above,
+ upperLeft,
+ aboveMinusUpper,
+ leftMinusUpper,
+ distanceLeft,
+ distanceAbove);
+ }
+
+ ///
+ /// Selects the nearest Paeth neighbor for sixty-four independent byte lanes.
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ private static Vector512 Predict(
+ Vector512 left,
+ Vector512 above,
+ Vector512 upperLeft)
+ {
+ Vector512 aboveMinusUpper = Avx512BW.SubtractSaturate(above, upperLeft);
+ Vector512 leftMinusUpper = Avx512BW.SubtractSaturate(left, upperLeft);
+ Vector512 distanceLeft = Avx512BW.SubtractSaturate(upperLeft, above) | aboveMinusUpper;
+ Vector512 distanceAbove = Avx512BW.SubtractSaturate(upperLeft, left) | leftMinusUpper;
+
+ return SelectPredictor(
+ left,
+ above,
+ upperLeft,
+ aboveMinusUpper,
+ leftMinusUpper,
+ distanceLeft,
+ distanceAbove);
+ }
+
+ ///
+ /// Applies Paeth distance and tie-breaking rules to sixteen lanes.
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ private static Vector128 SelectPredictor(
+ Vector128 left,
+ Vector128 above,
+ Vector128 upperLeft,
+ Vector128 aboveMinusUpper,
+ Vector128 leftMinusUpper,
+ Vector128 distanceLeft,
+ Vector128 distanceAbove)
+ {
+ Vector128 sameDirection = Vector128.Equals(
+ Vector128.Equals(aboveMinusUpper, Vector128.Zero),
+ Vector128.Equals(leftMinusUpper, Vector128.Zero));
+
+ Vector128 distanceUpper = sameDirection
+ | SubtractSaturate(distanceAbove, distanceLeft)
+ | SubtractSaturate(distanceLeft, distanceAbove);
+
+ Vector128 minimumAboveUpper = Vector128.Min(distanceUpper, distanceAbove);
+ Vector128 aboveOrUpper = Vector128.ConditionalSelect(
+ Vector128.Equals(minimumAboveUpper, distanceAbove),
+ above,
+ upperLeft);
+
+ // Applying the left comparison last preserves PNG's left-first tie rule.
+ return Vector128.ConditionalSelect(
+ Vector128.Equals(Vector128.Min(minimumAboveUpper, distanceLeft), distanceLeft),
+ left,
+ aboveOrUpper);
+ }
+
+ ///
+ /// Applies Paeth distance and tie-breaking rules to thirty-two lanes.
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ private static Vector256 SelectPredictor(
+ Vector256 left,
+ Vector256 above,
+ Vector256 upperLeft,
+ Vector256 aboveMinusUpper,
+ Vector256 leftMinusUpper,
+ Vector256 distanceLeft,
+ Vector256 distanceAbove)
+ {
+ Vector256 sameDirection = Vector256.Equals(
+ Vector256.Equals(aboveMinusUpper, Vector256.Zero),
+ Vector256.Equals(leftMinusUpper, Vector256.Zero));
+
+ Vector256 distanceUpper = sameDirection
+ | Avx2.SubtractSaturate(distanceAbove, distanceLeft)
+ | Avx2.SubtractSaturate(distanceLeft, distanceAbove);
+
+ Vector256 minimumAboveUpper = Vector256.Min(distanceUpper, distanceAbove);
+ Vector256 aboveOrUpper = Vector256.ConditionalSelect(
+ Vector256.Equals(minimumAboveUpper, distanceAbove),
+ above,
+ upperLeft);
+
+ return Vector256.ConditionalSelect(
+ Vector256.Equals(Vector256.Min(minimumAboveUpper, distanceLeft), distanceLeft),
+ left,
+ aboveOrUpper);
+ }
+
+ ///
+ /// Applies Paeth distance and tie-breaking rules to sixty-four lanes.
+ ///
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ private static Vector512 SelectPredictor(
+ Vector512 left,
+ Vector512 above,
+ Vector512 upperLeft,
+ Vector512 aboveMinusUpper,
+ Vector512 leftMinusUpper,
+ Vector512 distanceLeft,
+ Vector512 distanceAbove)
+ {
+ Vector512 sameDirection = Vector512.Equals(
+ Vector512.Equals(aboveMinusUpper, Vector512.Zero),
+ Vector512.Equals(leftMinusUpper, Vector512.Zero));
+
+ Vector512 distanceUpper = sameDirection
+ | Avx512BW.SubtractSaturate(distanceAbove, distanceLeft)
+ | Avx512BW.SubtractSaturate(distanceLeft, distanceAbove);
+
+ Vector512 minimumAboveUpper = Vector512.Min(distanceUpper, distanceAbove);
+ Vector512 aboveOrUpper = Vector512.ConditionalSelect(
+ Vector512.Equals(minimumAboveUpper, distanceAbove),
+ above,
+ upperLeft);
+
+ return Vector512.ConditionalSelect(
+ Vector512.Equals(Vector512.Min(minimumAboveUpper, distanceLeft), distanceLeft),
+ left,
+ aboveOrUpper);
+ }
+
+ ///
+ /// Performs an unsigned saturating subtraction using the active 128-bit instruction set.
+ ///
+ /// The minuend lanes.
+ /// The subtrahend lanes.
+ /// The saturated lane-wise differences.
+ [MethodImpl(InliningOptions.AlwaysInline)]
+ private static Vector128 SubtractSaturate(Vector128 left, Vector128 right)
+ {
+ if (Sse2.IsSupported)
+ {
+ return Sse2.SubtractSaturate(left, right);
+ }
+
+ if (AdvSimd.IsSupported)
+ {
+ return AdvSimd.SubtractSaturate(left, right);
+ }
+
+ // Subtracting the smaller operand produces max(left - right, 0) without
+ // requiring a backend-specific saturating-subtract instruction.
+ return left - Vector128.Min(left, right);
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
index 59c903c1d..0216a2627 100644
--- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
@@ -1,7 +1,6 @@
// 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;
@@ -193,86 +192,12 @@ internal static class PaethFilter
/// The sum of the total variance of the filtered row.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum)
- {
- DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
- DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
-
- ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
- ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
- ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
- sum = 0;
-
- // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp))
- resultBaseRef = (byte)FilterType.Paeth;
-
- nuint x = 0;
- for (; x < (uint)bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */)
- {
- byte scan = Unsafe.Add(ref scanBaseRef, x);
- byte above = Unsafe.Add(ref prevBaseRef, x);
- ++x;
- ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
- res = (byte)(scan - PaethPredictor(0, above, 0));
- sum += Numerics.Abs(unchecked((sbyte)res));
- }
-
- if (Avx2.IsSupported)
- {
- Vector256 zero = Vector256.Zero;
- Vector256 sumAccumulator = Vector256.Zero;
-
- for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count)
- {
- Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
- Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft));
- Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x));
- Vector256 upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft));
-
- Vector256 res = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft));
- Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
- x += (uint)Vector256.Count;
-
- sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
- }
-
- sum += Numerics.EvenReduceSum(sumAccumulator);
- }
- else if (Vector.IsHardwareAccelerated)
- {
- Vector sumAccumulator = Vector.Zero;
-
- for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector.Count; xLeft += (uint)Vector.Count)
- {
- Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
- Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft));
- Vector above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x));
- Vector upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft));
-
- Vector res = scan - PaethPredictor(left, above, upperLeft);
- Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
- x += (uint)Vector.Count;
-
- Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res))));
- }
-
- for (int i = 0; i < Vector.Count; i++)
- {
- sum += (int)sumAccumulator[i];
- }
- }
-
- for (nuint xLeft = x - (uint)bytesPerPixel; (int)x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
- {
- byte scan = Unsafe.Add(ref scanBaseRef, x);
- byte left = Unsafe.Add(ref scanBaseRef, xLeft);
- byte above = Unsafe.Add(ref prevBaseRef, x);
- byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft);
- ++x;
- ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
- res = (byte)(scan - PaethPredictor(left, above, upperLeft));
- sum += Numerics.Abs(unchecked((sbyte)res));
- }
- }
+ => PngFilterEncoder.Encode(
+ scanline,
+ previousScanline,
+ result,
+ (uint)bytesPerPixel,
+ out sum);
///
/// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses
@@ -304,70 +229,4 @@ internal static class PaethFilter
return upperLeft;
}
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Vector256 PaethPredictor(Vector256 left, Vector256 above, Vector256 upleft)
- {
- Vector256 zero = Vector256.Zero;
-
- // Here, we refactor pa = abs(p - left) = abs(left + above - upleft - left)
- // to pa = abs(above - upleft). Same deal for pb.
- // Using saturated subtraction, if the result is negative, the output is zero.
- // If we subtract in both directions and `or` the results, only one can be
- // non-zero, so we end up with the absolute value.
- Vector256 sac = Avx2.SubtractSaturate(above, upleft);
- Vector256 sbc = Avx2.SubtractSaturate(left, upleft);
- Vector256 pa = Avx2.Or(Avx2.SubtractSaturate(upleft, above), sac);
- Vector256 pb = Avx2.Or(Avx2.SubtractSaturate(upleft, left), sbc);
-
- // pc = abs(left + above - upleft - upleft), or abs(left - upleft + above - upleft).
- // We've already calculated left - upleft and above - upleft in `sac` and `sbc`.
- // If they are both negative or both positive, the absolute value of their
- // sum can't possibly be less than `pa` or `pb`, so we'll never use the value.
- // We make a mask that sets the value to 255 if they either both got
- // saturated to zero or both didn't. Then we calculate the absolute value
- // of their difference using saturated subtract and `or`, same as before,
- // keeping the value only where the mask isn't set.
- Vector256 pm = Avx2.CompareEqual(Avx2.CompareEqual(sac, zero), Avx2.CompareEqual(sbc, zero));
- Vector256 pc = Avx2.Or(pm, Avx2.Or(Avx2.SubtractSaturate(pb, pa), Avx2.SubtractSaturate(pa, pb)));
-
- // Finally, blend the values together. We start with `upleft` and overwrite on
- // tied values so that the `left`, `above`, `upleft` precedence is preserved.
- Vector256 minbc = Avx2.Min(pc, pb);
- Vector256 resbc = Avx2.BlendVariable(upleft, above, Avx2.CompareEqual(minbc, pb));
- return Avx2.BlendVariable(resbc, left, Avx2.CompareEqual(Avx2.Min(minbc, pa), pa));
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Vector PaethPredictor(Vector left, Vector above, Vector upperLeft)
- {
- Vector.Widen(left, out Vector a1, out Vector a2);
- Vector.Widen(above, out Vector b1, out Vector b2);
- Vector.Widen(upperLeft, out Vector c1, out Vector c2);
-
- Vector p1 = PaethPredictor(Vector.AsVectorInt16(a1), Vector.AsVectorInt16(b1), Vector.AsVectorInt16(c1));
- Vector p2 = PaethPredictor(Vector.AsVectorInt16(a2), Vector.AsVectorInt16(b2), Vector.AsVectorInt16(c2));
- return Vector.AsVectorByte(Vector.Narrow(p1, p2));
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Vector PaethPredictor(Vector