diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation.cs b/src/ImageSharp/Advanced/IRowIntervalOperation.cs
new file mode 100644
index 000000000..3e1b08621
--- /dev/null
+++ b/src/ImageSharp/Advanced/IRowIntervalOperation.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Advanced
+{
+ ///
+ /// Defines the contract for an action that operates on a row interval.
+ ///
+ public interface IRowIntervalOperation
+ {
+ ///
+ /// Invokes the method passing the row interval.
+ ///
+ /// The row interval.
+ void Invoke(in RowInterval rows);
+ }
+}
diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs
new file mode 100644
index 000000000..c18842a92
--- /dev/null
+++ b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Advanced
+{
+ ///
+ /// Defines the contract for an action that operates on a row interval with a temporary buffer.
+ ///
+ /// The type of buffer elements.
+ public interface IRowIntervalOperation
+ where TBuffer : unmanaged
+ {
+ ///
+ /// Invokes the method passing the row interval and a buffer.
+ ///
+ /// The row interval.
+ /// The contiguous region of memory.
+ void Invoke(in RowInterval rows, Span span);
+ }
+}
diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs
similarity index 95%
rename from src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs
rename to src/ImageSharp/Advanced/ParallelExecutionSettings.cs
index f17d70a2a..54ee06918 100644
--- a/src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs
+++ b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -6,10 +6,10 @@ using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
-namespace SixLabors.ImageSharp.Advanced.ParallelUtils
+namespace SixLabors.ImageSharp.Advanced
{
///
- /// Defines execution settings for methods in .
+ /// Defines execution settings for methods in .
///
public readonly struct ParallelExecutionSettings
{
@@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
}
///
- /// Get the default for a
+ /// Get the default for a
///
/// The .
/// The .
diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
new file mode 100644
index 000000000..adbad0d66
--- /dev/null
+++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
@@ -0,0 +1,110 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Advanced
+{
+ ///
+ /// Utility methods for batched processing of pixel row intervals.
+ /// Parallel execution is optimized for image processing based on values defined
+ /// or .
+ /// Using this class is preferred over direct usage of utility methods.
+ ///
+ public static partial class ParallelRowIterator
+ {
+ private readonly struct IterationParameters
+ {
+ public readonly int MinY;
+ public readonly int MaxY;
+ public readonly int StepY;
+ public readonly int Width;
+
+ public IterationParameters(int minY, int maxY, int stepY)
+ : this(minY, maxY, stepY, 0)
+ {
+ }
+
+ public IterationParameters(int minY, int maxY, int stepY, int width)
+ {
+ this.MinY = minY;
+ this.MaxY = maxY;
+ this.StepY = stepY;
+ this.Width = width;
+ }
+ }
+
+ private readonly struct RowIntervalOperationWrapper
+ where T : struct, IRowIntervalOperation
+ {
+ private readonly IterationParameters info;
+ private readonly T operation;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperationWrapper(in IterationParameters info, in T operation)
+ {
+ this.info = info;
+ this.operation = operation;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(int i)
+ {
+ int yMin = this.info.MinY + (i * this.info.StepY);
+
+ if (yMin >= this.info.MaxY)
+ {
+ return;
+ }
+
+ int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY);
+ var rows = new RowInterval(yMin, yMax);
+
+ // Skip the safety copy when invoking a potentially impure method on a readonly field
+ Unsafe.AsRef(in this.operation).Invoke(in rows);
+ }
+ }
+
+ private readonly struct RowIntervalOperationWrapper
+ where T : struct, IRowIntervalOperation
+ where TBuffer : unmanaged
+ {
+ private readonly IterationParameters info;
+ private readonly MemoryAllocator allocator;
+ private readonly T operation;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperationWrapper(
+ in IterationParameters info,
+ MemoryAllocator allocator,
+ in T operation)
+ {
+ this.info = info;
+ this.allocator = allocator;
+ this.operation = operation;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(int i)
+ {
+ int yMin = this.info.MinY + (i * this.info.StepY);
+
+ if (yMin >= this.info.MaxY)
+ {
+ return;
+ }
+
+ int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY);
+ var rows = new RowInterval(yMin, yMax);
+
+ using IMemoryOwner buffer = this.allocator.Allocate(this.info.Width);
+
+ Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs
new file mode 100644
index 000000000..123784c57
--- /dev/null
+++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs
@@ -0,0 +1,162 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Advanced
+{
+ ///
+ /// Utility methods for batched processing of pixel row intervals.
+ /// Parallel execution is optimized for image processing based on values defined
+ /// or .
+ /// Using this class is preferred over direct usage of utility methods.
+ ///
+ public static partial class ParallelRowIterator
+ {
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s.
+ ///
+ /// The type of row operation to perform.
+ /// The to get the parallel settings from.
+ /// The .
+ /// The operation defining the iteration logic on a single .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation)
+ where T : struct, IRowIntervalOperation
+ {
+ var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+ IterateRows(rectangle, in parallelSettings, in operation);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s.
+ ///
+ /// The type of row operation to perform.
+ /// The .
+ /// The .
+ /// The operation defining the iteration logic on a single .
+ public static void IterateRows(
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ in T operation)
+ where T : struct, IRowIntervalOperation
+ {
+ ValidateRectangle(rectangle);
+
+ int top = rectangle.Top;
+ int bottom = rectangle.Bottom;
+ int width = rectangle.Width;
+ int height = rectangle.Height;
+
+ int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
+
+ // Avoid TPL overhead in this trivial case:
+ if (numOfSteps == 1)
+ {
+ var rows = new RowInterval(top, bottom);
+ Unsafe.AsRef(in operation).Invoke(in rows);
+ return;
+ }
+
+ int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
+ var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
+ var info = new IterationParameters(top, bottom, verticalStep);
+ var wrappingOperation = new RowIntervalOperationWrapper(in info, in operation);
+
+ Parallel.For(
+ 0,
+ numOfSteps,
+ parallelOptions,
+ wrappingOperation.Invoke);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s
+ /// instantiating a temporary buffer for each invocation.
+ ///
+ /// The type of row operation to perform.
+ /// The type of buffer elements.
+ /// The to get the parallel settings from.
+ /// The .
+ /// The operation defining the iteration logic on a single .
+ public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation)
+ where T : struct, IRowIntervalOperation
+ where TBuffer : unmanaged
+ {
+ var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+ IterateRows(rectangle, in parallelSettings, in operation);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s
+ /// instantiating a temporary buffer for each invocation.
+ ///
+ /// The type of row operation to perform.
+ /// The type of buffer elements.
+ /// The .
+ /// The .
+ /// The operation defining the iteration logic on a single .
+ public static void IterateRows(
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ in T operation)
+ where T : struct, IRowIntervalOperation
+ where TBuffer : unmanaged
+ {
+ ValidateRectangle(rectangle);
+
+ int top = rectangle.Top;
+ int bottom = rectangle.Bottom;
+ int width = rectangle.Width;
+ int height = rectangle.Height;
+
+ int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
+ MemoryAllocator allocator = parallelSettings.MemoryAllocator;
+
+ // Avoid TPL overhead in this trivial case:
+ if (numOfSteps == 1)
+ {
+ var rows = new RowInterval(top, bottom);
+ using (IMemoryOwner buffer = allocator.Allocate(width))
+ {
+ Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span);
+ }
+
+ return;
+ }
+
+ int verticalStep = DivideCeil(height, numOfSteps);
+ var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
+ var info = new IterationParameters(top, bottom, verticalStep, width);
+ var wrappingOperation = new RowIntervalOperationWrapper(in info, allocator, in operation);
+
+ Parallel.For(
+ 0,
+ numOfSteps,
+ parallelOptions,
+ wrappingOperation.Invoke);
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);
+
+ private static void ValidateRectangle(Rectangle rectangle)
+ {
+ Guard.MustBeGreaterThan(
+ rectangle.Width,
+ 0,
+ $"{nameof(rectangle)}.{nameof(rectangle.Width)}");
+
+ Guard.MustBeGreaterThan(
+ rectangle.Height,
+ 0,
+ $"{nameof(rectangle)}.{nameof(rectangle.Height)}");
+ }
+ }
+}
diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
deleted file mode 100644
index 0414f3ae3..000000000
--- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Buffers;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Advanced.ParallelUtils
-{
- ///
- /// Utility methods for batched processing of pixel row intervals.
- /// Parallel execution is optimized for image processing based on values defined
- /// or .
- /// Using this class is preferred over direct usage of utility methods.
- ///
- public static class ParallelHelper
- {
- ///
- /// Iterate through the rows of a rectangle in optimized batches defined by -s.
- ///
- /// The .
- /// The to get the parallel settings from.
- /// The method body defining the iteration logic on a single .
- public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body)
- {
- var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
-
- IterateRows(rectangle, parallelSettings, body);
- }
-
- ///
- /// Iterate through the rows of a rectangle in optimized batches defined by -s.
- ///
- /// The .
- /// The .
- /// The method body defining the iteration logic on a single .
- public static void IterateRows(
- Rectangle rectangle,
- in ParallelExecutionSettings parallelSettings,
- Action body)
- {
- ValidateRectangle(rectangle);
-
- int maxSteps = DivideCeil(
- rectangle.Width * rectangle.Height,
- parallelSettings.MinimumPixelsProcessedPerTask);
-
- int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
-
- // Avoid TPL overhead in this trivial case:
- if (numOfSteps == 1)
- {
- var rows = new RowInterval(rectangle.Top, rectangle.Bottom);
- body(rows);
- return;
- }
-
- int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
-
- var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
-
- int top = rectangle.Top;
- int bottom = rectangle.Bottom;
-
- Parallel.For(
- 0,
- numOfSteps,
- parallelOptions,
- i =>
- {
- int yMin = top + (i * verticalStep);
-
- if (yMin >= bottom)
- {
- return;
- }
-
- int yMax = Math.Min(yMin + verticalStep, bottom);
-
- var rows = new RowInterval(yMin, yMax);
- body(rows);
- });
- }
-
- ///
- /// Iterate through the rows of a rectangle in optimized batches defined by -s
- /// instantiating a temporary buffer for each invocation.
- ///
- internal static void IterateRowsWithTempBuffer(
- Rectangle rectangle,
- in ParallelExecutionSettings parallelSettings,
- Action> body)
- where T : unmanaged
- {
- ValidateRectangle(rectangle);
-
- int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask);
-
- int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
-
- MemoryAllocator memoryAllocator = parallelSettings.MemoryAllocator;
-
- // Avoid TPL overhead in this trivial case:
- if (numOfSteps == 1)
- {
- var rows = new RowInterval(rectangle.Top, rectangle.Bottom);
- using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width))
- {
- body(rows, buffer.Memory);
- }
-
- return;
- }
-
- int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
-
- var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
-
- int top = rectangle.Top;
- int bottom = rectangle.Bottom;
-
- Parallel.For(
- 0,
- numOfSteps,
- parallelOptions,
- i =>
- {
- int yMin = top + (i * verticalStep);
-
- if (yMin >= bottom)
- {
- return;
- }
-
- int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom);
-
- var rows = new RowInterval(yMin, yMax);
-
- using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width))
- {
- body(rows, buffer.Memory);
- }
- });
- }
-
- ///
- /// Iterate through the rows of a rectangle in optimized batches defined by -s
- /// instantiating a temporary buffer for each invocation.
- ///
- internal static void IterateRowsWithTempBuffer(
- Rectangle rectangle,
- Configuration configuration,
- Action> body)
- where T : unmanaged
- {
- IterateRowsWithTempBuffer(rectangle, ParallelExecutionSettings.FromConfiguration(configuration), body);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);
-
- private static void ValidateRectangle(Rectangle rectangle)
- {
- Guard.MustBeGreaterThan(
- rectangle.Width,
- 0,
- $"{nameof(rectangle)}.{nameof(rectangle.Width)}");
-
- Guard.MustBeGreaterThan(
- rectangle.Height,
- 0,
- $"{nameof(rectangle)}.{nameof(rectangle.Height)}");
- }
- }
-}
diff --git a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs
index cff6e3b60..983a1eb8b 100644
--- a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs
+++ b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs
@@ -1,10 +1,10 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
-namespace SixLabors.ImageSharp.Common
+namespace SixLabors.ImageSharp
{
///
/// Encapsulates a series of time saving extension methods to the interface.
@@ -34,15 +34,11 @@ namespace SixLabors.ImageSharp.Common
///
/// Generates a sequence of integral numbers within a specified range.
///
- ///
- /// The start index, inclusive.
- ///
+ /// The start index, inclusive.
///
/// A method that has one parameter and returns a calculating the end index.
///
- ///
- /// The incremental step.
- ///
+ /// The incremental step.
///
/// The that contains a range of sequential integral numbers.
///
@@ -56,4 +52,4 @@ namespace SixLabors.ImageSharp.Common
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs
index e1112c017..a2de8d671 100644
--- a/src/ImageSharp/ImageFrame{TPixel}.cs
+++ b/src/ImageSharp/ImageFrame{TPixel}.cs
@@ -4,9 +4,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@@ -261,19 +259,12 @@ namespace SixLabors.ImageSharp
}
var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone());
+ var operation = new RowIntervalOperation(this, target, configuration);
- ParallelHelper.IterateRows(
- this.Bounds(),
+ ParallelRowIterator.IterateRows(
configuration,
- rows =>
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span sourceRow = this.GetPixelRowSpan(y);
- Span targetRow = target.GetPixelRowSpan(y);
- PixelOperations.Instance.To(configuration, sourceRow, targetRow);
- }
- });
+ this.Bounds(),
+ in operation);
return target;
}
@@ -295,5 +286,39 @@ namespace SixLabors.ImageSharp
span.Fill(value);
}
}
+
+ ///
+ /// A implementing the clone logic for .
+ ///
+ private readonly struct RowIntervalOperation : IRowIntervalOperation
+ where TPixel2 : struct, IPixel
+ {
+ private readonly ImageFrame source;
+ private readonly ImageFrame target;
+ private readonly Configuration configuration;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperation(
+ ImageFrame source,
+ ImageFrame target,
+ Configuration configuration)
+ {
+ this.source = source;
+ this.target = target;
+ this.configuration = configuration;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows)
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span sourceRow = this.source.GetPixelRowSpan(y);
+ Span targetRow = this.target.GetPixelRowSpan(y);
+ PixelOperations.Instance.To(this.configuration, sourceRow, targetRow);
+ }
+ }
+ }
}
}
diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj
index 0fd449d90..be0e9032b 100644
--- a/src/ImageSharp/ImageSharp.csproj
+++ b/src/ImageSharp/ImageSharp.csproj
@@ -24,9 +24,9 @@
-
-
-
+
+
+
diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs
index 0350c669a..9c3a592d7 100644
--- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs
+++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -126,4 +126,4 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs
index 95b01fd9d..d391057a9 100644
--- a/src/ImageSharp/Primitives/Rectangle.cs
+++ b/src/ImageSharp/Primitives/Rectangle.cs
@@ -460,4 +460,4 @@ namespace SixLabors.ImageSharp
this.Width.Equals(other.Width) &&
this.Height.Equals(other.Height);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
index 380ce64d2..ed14a44e9 100644
--- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
@@ -2,9 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
-
+using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Advanced.ParallelUtils;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization
@@ -42,36 +42,66 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
Configuration configuration = this.Configuration;
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
- int startY = interest.Y;
- int endY = interest.Bottom;
- int startX = interest.X;
- int endX = interest.Right;
-
bool isAlphaOnly = typeof(TPixel) == typeof(A8);
- var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY);
-
- ParallelHelper.IterateRows(
- workingRect,
+ var operation = new RowIntervalOperation(interest, source, upper, lower, threshold, isAlphaOnly);
+ ParallelRowIterator.IterateRows(
configuration,
- rows =>
- {
- Rgba32 rgba = default;
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span row = source.GetPixelRowSpan(y);
+ interest,
+ in operation);
+ }
+
+ ///
+ /// A implementing the clone logic for .
+ ///
+ private readonly struct RowIntervalOperation : IRowIntervalOperation
+ {
+ private readonly ImageFrame source;
+ private readonly TPixel upper;
+ private readonly TPixel lower;
+ private readonly byte threshold;
+ private readonly int minX;
+ private readonly int maxX;
+ private readonly bool isAlphaOnly;
- for (int x = startX; x < endX; x++)
- {
- ref TPixel color = ref row[x];
- color.ToRgba32(ref rgba);
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperation(
+ Rectangle bounds,
+ ImageFrame source,
+ TPixel upper,
+ TPixel lower,
+ byte threshold,
+ bool isAlphaOnly)
+ {
+ this.source = source;
+ this.upper = upper;
+ this.lower = lower;
+ this.threshold = threshold;
+ this.minX = bounds.X;
+ this.maxX = bounds.Right;
+ this.isAlphaOnly = isAlphaOnly;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows)
+ {
+ Rgba32 rgba = default;
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span row = this.source.GetPixelRowSpan(y);
+
+ for (int x = this.minX; x < this.maxX; x++)
+ {
+ ref TPixel color = ref row[x];
+ color.ToRgba32(ref rgba);
- // Convert to grayscale using ITU-R Recommendation BT.709 if required
- byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
- color = luminance >= threshold ? upper : lower;
- }
- }
- });
+ // Convert to grayscale using ITU-R Recommendation BT.709 if required
+ byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
+ color = luminance >= this.threshold ? this.upper : this.lower;
+ }
+ }
+ }
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
index 316579da7..1ebd6476e 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
@@ -7,8 +7,7 @@ using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
-using SixLabors.ImageSharp.Advanced.ParallelUtils;
+using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters;
@@ -269,17 +268,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
protected override void OnFrameApply(ImageFrame source)
{
// Preliminary gamma highlight pass
- this.ApplyGammaExposure(source.PixelBuffer, this.SourceRectangle, this.Configuration);
+ var gammaOperation = new ApplyGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma);
+ ParallelRowIterator.IterateRows(
+ this.Configuration,
+ this.SourceRectangle,
+ in gammaOperation);
// Create a 0-filled buffer to use to store the result of the component convolutions
- using (Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean))
- {
- // Perform the 1D convolutions on all the kernel components and accumulate the results
- this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processingBuffer);
+ using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean);
- // Apply the inverse gamma exposure pass, and write the final pixel data
- this.ApplyInverseGammaExposure(source.PixelBuffer, processingBuffer, this.SourceRectangle, this.Configuration);
- }
+ // Perform the 1D convolutions on all the kernel components and accumulate the results
+ this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processingBuffer);
+
+ float inverseGamma = 1 / this.gamma;
+
+ // Apply the inverse gamma exposure pass, and write the final pixel data
+ var operation = new ApplyInverseGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma);
+ ParallelRowIterator.IterateRows(
+ this.Configuration,
+ this.SourceRectangle,
+ in operation);
}
///
@@ -295,216 +303,223 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Configuration configuration,
Buffer2D processingBuffer)
{
- using (Buffer2D firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size()))
+ // Allocate the buffer with the intermediate convolution results
+ using Buffer2D firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size());
+
+ // Perform two 1D convolutions for each component in the current instance
+ ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan());
+ ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan());
+ for (int i = 0; i < this.kernels.Length; i++)
{
- // Perform two 1D convolutions for each component in the current instance
- ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan());
- ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan());
- for (int i = 0; i < this.kernels.Length; i++)
- {
- // Compute the resulting complex buffer for the current component
- var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
- Complex64[] kernel = Unsafe.Add(ref baseRef, i);
- Vector4 parameters = Unsafe.Add(ref paramsRef, i);
-
- // Compute the two 1D convolutions and accumulate the partial results on the target buffer
- this.ApplyConvolution(firstPassBuffer, source.PixelBuffer, interest, kernel, configuration);
- this.ApplyConvolution(processingBuffer, firstPassBuffer, interest, kernel, configuration, parameters.Z, parameters.W);
- }
+ // Compute the resulting complex buffer for the current component
+ Complex64[] kernel = Unsafe.Add(ref baseRef, i);
+ Vector4 parameters = Unsafe.Add(ref paramsRef, i);
+
+ // Compute the vertical 1D convolution
+ var verticalOperation = new ApplyVerticalConvolutionRowIntervalOperation(sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel);
+ ParallelRowIterator.IterateRows(
+ configuration,
+ sourceRectangle,
+ in verticalOperation);
+
+ // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer
+ var horizontalOperation = new ApplyHorizontalConvolutionRowIntervalOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W);
+ ParallelRowIterator.IterateRows(
+ configuration,
+ sourceRectangle,
+ in horizontalOperation);
}
}
///
- /// Applies the process to the specified portion of the specified at the specified location
- /// and with the specified size.
+ /// A implementing the vertical convolution logic for .
///
- /// The target values to use to store the results.
- /// The source pixels. Cannot be null.
- ///
- /// The structure that specifies the portion of the image object to draw.
- ///
- /// The 1D kernel.
- /// The
- private void ApplyConvolution(
- Buffer2D targetValues,
- Buffer2D sourcePixels,
- Rectangle sourceRectangle,
- Complex64[] kernel,
- Configuration configuration)
+ private readonly struct ApplyVerticalConvolutionRowIntervalOperation : IRowIntervalOperation
{
- int startY = sourceRectangle.Y;
- int endY = sourceRectangle.Bottom;
- int startX = sourceRectangle.X;
- int endX = sourceRectangle.Right;
- int maxY = endY - 1;
- int maxX = endX - 1;
-
- var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
- int width = workingRectangle.Width;
-
- ParallelHelper.IterateRows(
- workingRectangle,
- configuration,
- rows =>
+ private readonly Rectangle bounds;
+ private readonly Buffer2D targetValues;
+ private readonly Buffer2D sourcePixels;
+ private readonly Complex64[] kernel;
+ private readonly int maxY;
+ private readonly int maxX;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public ApplyVerticalConvolutionRowIntervalOperation(
+ Rectangle bounds,
+ Buffer2D targetValues,
+ Buffer2D sourcePixels,
+ Complex64[] kernel)
+ {
+ this.bounds = bounds;
+ this.maxY = this.bounds.Bottom - 1;
+ this.maxX = this.bounds.Right - 1;
+ this.targetValues = targetValues;
+ this.sourcePixels = sourcePixels;
+ this.kernel = kernel;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows)
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
{
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX);
+ Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X);
- for (int x = 0; x < width; x++)
- {
- Buffer2DUtils.Convolve4(kernel, sourcePixels, targetRowSpan, y, x, startY, maxY, startX, maxX);
- }
+ for (int x = 0; x < this.bounds.Width; x++)
+ {
+ Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX);
}
- });
+ }
+ }
}
///
- /// Applies the process to the specified portion of the specified buffer at the specified location
- /// and with the specified size.
+ /// A implementing the horizontal convolution logic for .
///
- /// The target values to use to store the results.
- /// The source complex values. Cannot be null.
- /// The structure that specifies the portion of the image object to draw.
- /// The 1D kernel.
- /// The
- /// The weight factor for the real component of the complex pixel values.
- /// The weight factor for the imaginary component of the complex pixel values.
- private void ApplyConvolution(
- Buffer2D targetValues,
- Buffer2D sourceValues,
- Rectangle sourceRectangle,
- Complex64[] kernel,
- Configuration configuration,
- float z,
- float w)
+ private readonly struct ApplyHorizontalConvolutionRowIntervalOperation : IRowIntervalOperation
{
- int startY = sourceRectangle.Y;
- int endY = sourceRectangle.Bottom;
- int startX = sourceRectangle.X;
- int endX = sourceRectangle.Right;
- int maxY = endY - 1;
- int maxX = endX - 1;
-
- var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
- int width = workingRectangle.Width;
-
- ParallelHelper.IterateRows(
- workingRectangle,
- configuration,
- rows =>
+ private readonly Rectangle bounds;
+ private readonly Buffer2D targetValues;
+ private readonly Buffer2D sourceValues;
+ private readonly Complex64[] kernel;
+ private readonly float z;
+ private readonly float w;
+ private readonly int maxY;
+ private readonly int maxX;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public ApplyHorizontalConvolutionRowIntervalOperation(
+ Rectangle bounds,
+ Buffer2D targetValues,
+ Buffer2D sourceValues,
+ Complex64[] kernel,
+ float z,
+ float w)
+ {
+ this.bounds = bounds;
+ this.maxY = this.bounds.Bottom - 1;
+ this.maxX = this.bounds.Right - 1;
+ this.targetValues = targetValues;
+ this.sourceValues = sourceValues;
+ this.kernel = kernel;
+ this.z = z;
+ this.w = w;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows)
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
{
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX);
+ Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X);
- for (int x = 0; x < width; x++)
- {
- Buffer2DUtils.Convolve4AndAccumulatePartials(kernel, sourceValues, targetRowSpan, y, x, startY, maxY, startX, maxX, z, w);
- }
+ for (int x = 0; x < this.bounds.Width; x++)
+ {
+ Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w);
}
- });
+ }
+ }
}
///
- /// Applies the gamma correction/highlight to the input pixel buffer.
+ /// A implementing the gamma exposure logic for .
///
- /// The target pixel buffer to adjust.
- ///
- /// The structure that specifies the portion of the image object to draw.
- ///
- /// The
- private void ApplyGammaExposure(
- Buffer2D targetPixels,
- Rectangle sourceRectangle,
- Configuration configuration)
+ private readonly struct ApplyGammaExposureRowIntervalOperation : IRowIntervalOperation
{
- int startY = sourceRectangle.Y;
- int endY = sourceRectangle.Bottom;
- int startX = sourceRectangle.X;
- int endX = sourceRectangle.Right;
-
- var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
- int width = workingRectangle.Width;
- float exp = this.gamma;
-
- ParallelHelper.IterateRowsWithTempBuffer(
- workingRectangle,
- configuration,
- (rows, vectorBuffer) =>
+ private readonly Rectangle bounds;
+ private readonly Buffer2D targetPixels;
+ private readonly Configuration configuration;
+ private readonly float gamma;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public ApplyGammaExposureRowIntervalOperation(
+ Rectangle bounds,
+ Buffer2D targetPixels,
+ Configuration configuration,
+ float gamma)
+ {
+ this.bounds = bounds;
+ this.targetPixels = targetPixels;
+ this.configuration = configuration;
+ this.gamma = gamma;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows, Span span)
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
+ PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply);
+ ref Vector4 baseRef = ref MemoryMarshal.GetReference(span);
+
+ for (int x = 0; x < this.bounds.Width; x++)
{
- Span vectorSpan = vectorBuffer.Span;
- int length = vectorSpan.Length;
-
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
- PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply);
- ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan);
-
- for (int x = 0; x < width; x++)
- {
- ref Vector4 v = ref Unsafe.Add(ref baseRef, x);
- v.X = MathF.Pow(v.X, exp);
- v.Y = MathF.Pow(v.Y, exp);
- v.Z = MathF.Pow(v.Z, exp);
- }
-
- PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan);
- }
- });
+ ref Vector4 v = ref Unsafe.Add(ref baseRef, x);
+ v.X = MathF.Pow(v.X, this.gamma);
+ v.Y = MathF.Pow(v.Y, this.gamma);
+ v.Z = MathF.Pow(v.Z, this.gamma);
+ }
+
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
+ }
+ }
}
///
- /// Applies the inverse gamma correction/highlight pass, and converts the input buffer into pixel values.
+ /// A implementing the inverse gamma exposure logic for .
///
- /// The target pixels to apply the process to.
- /// The source values. Cannot be null.
- ///
- /// The structure that specifies the portion of the image object to draw.
- ///
- /// The
- private void ApplyInverseGammaExposure(
- Buffer2D targetPixels,
- Buffer2D sourceValues,
- Rectangle sourceRectangle,
- Configuration configuration)
+ private readonly struct ApplyInverseGammaExposureRowIntervalOperation : IRowIntervalOperation
{
- int startY = sourceRectangle.Y;
- int endY = sourceRectangle.Bottom;
- int startX = sourceRectangle.X;
- int endX = sourceRectangle.Right;
-
- var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
- int width = workingRectangle.Width;
- float expGamma = 1 / this.gamma;
-
- ParallelHelper.IterateRows(
- workingRectangle,
- configuration,
- rows =>
+ private readonly Rectangle bounds;
+ private readonly Buffer2D targetPixels;
+ private readonly Buffer2D sourceValues;
+ private readonly Configuration configuration;
+ private readonly float inverseGamma;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public ApplyInverseGammaExposureRowIntervalOperation(
+ Rectangle bounds,
+ Buffer2D targetPixels,
+ Buffer2D sourceValues,
+ Configuration configuration,
+ float inverseGamma)
+ {
+ this.bounds = bounds;
+ this.targetPixels = targetPixels;
+ this.sourceValues = sourceValues;
+ this.configuration = configuration;
+ this.inverseGamma = inverseGamma;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows)
+ {
+ Vector4 low = Vector4.Zero;
+ var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
+
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
+ Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X);
+ ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan);
+
+ for (int x = 0; x < this.bounds.Width; x++)
{
- Vector4 low = Vector4.Zero;
- var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
-
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span targetPixelSpan = targetPixels.GetRowSpan(y).Slice(startX);
- Span sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX);
- ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan);
-
- for (int x = 0; x < width; x++)
- {
- ref Vector4 v = ref Unsafe.Add(ref sourceRef, x);
- var clamp = Vector4.Clamp(v, low, high);
- v.X = MathF.Pow(clamp.X, expGamma);
- v.Y = MathF.Pow(clamp.Y, expGamma);
- v.Z = MathF.Pow(clamp.Z, expGamma);
- }
-
- PixelOperations.Instance.FromVector4Destructive(configuration, sourceRowSpan.Slice(0, width), targetPixelSpan, PixelConversionModifiers.Premultiply);
- }
- });
+ ref Vector4 v = ref Unsafe.Add(ref sourceRef, x);
+ var clamp = Vector4.Clamp(v, low, high);
+ v.X = MathF.Pow(clamp.X, this.inverseGamma);
+ v.Y = MathF.Pow(clamp.Y, this.inverseGamma);
+ v.Z = MathF.Pow(clamp.Z, this.inverseGamma);
+ }
+
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply);
+ }
+ }
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
index 095c91bac..50004655f 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
@@ -40,10 +40,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source)
{
- using (var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
- {
- processor.Apply(source);
- }
+ using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle);
+
+ processor.Apply(source);
}
///
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
index c2b85a4ab..1c4987c79 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
@@ -3,9 +3,9 @@
using System;
using System.Numerics;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
-using SixLabors.ImageSharp.Advanced.ParallelUtils;
+using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -60,79 +60,105 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source)
{
- DenseMatrix matrixY = this.KernelY;
- DenseMatrix matrixX = this.KernelX;
- bool preserveAlpha = this.PreserveAlpha;
+ using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height);
+
+ source.CopyTo(targetPixels);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
- int startY = interest.Y;
- int endY = interest.Bottom;
- int startX = interest.X;
- int endX = interest.Right;
- int maxY = endY - 1;
- int maxX = endX - 1;
-
- using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height))
+ var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha);
+
+ ParallelRowIterator.IterateRows(
+ this.Configuration,
+ interest,
+ in operation);
+
+ Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
+ }
+
+ ///
+ /// A implementing the convolution logic for .
+ ///
+ private readonly struct RowIntervalOperation : IRowIntervalOperation
+ {
+ private readonly Rectangle bounds;
+ private readonly int maxY;
+ private readonly int maxX;
+ private readonly Buffer2D targetPixels;
+ private readonly Buffer2D sourcePixels;
+ private readonly DenseMatrix kernelY;
+ private readonly DenseMatrix kernelX;
+ private readonly Configuration configuration;
+ private readonly bool preserveAlpha;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperation(
+ Rectangle bounds,
+ Buffer2D targetPixels,
+ Buffer2D sourcePixels,
+ DenseMatrix kernelY,
+ DenseMatrix kernelX,
+ Configuration configuration,
+ bool preserveAlpha)
{
- source.CopyTo(targetPixels);
+ this.bounds = bounds;
+ this.maxY = this.bounds.Bottom - 1;
+ this.maxX = this.bounds.Right - 1;
+ this.targetPixels = targetPixels;
+ this.sourcePixels = sourcePixels;
+ this.kernelY = kernelY;
+ this.kernelX = kernelX;
+ this.configuration = configuration;
+ this.preserveAlpha = preserveAlpha;
+ }
- var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
- int width = workingRectangle.Width;
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows, Span span)
+ {
+ ref Vector4 spanRef = ref MemoryMarshal.GetReference(span);
- ParallelHelper.IterateRowsWithTempBuffer(
- workingRectangle,
- this.Configuration,
- (rows, vectorBuffer) =>
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
+ PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
+
+ if (this.preserveAlpha)
+ {
+ for (int x = 0; x < this.bounds.Width; x++)
+ {
+ DenseMatrixUtils.Convolve2D3(
+ in this.kernelY,
+ in this.kernelX,
+ this.sourcePixels,
+ ref spanRef,
+ y,
+ x,
+ this.bounds.Y,
+ this.maxY,
+ this.bounds.X,
+ this.maxX);
+ }
+ }
+ else
+ {
+ for (int x = 0; x < this.bounds.Width; x++)
{
- Span vectorSpan = vectorBuffer.Span;
- int length = vectorSpan.Length;
- ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
-
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
- PixelOperations.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan);
-
- if (preserveAlpha)
- {
- for (int x = 0; x < width; x++)
- {
- DenseMatrixUtils.Convolve2D3(
- in matrixY,
- in matrixX,
- source.PixelBuffer,
- ref vectorSpanRef,
- y,
- x,
- startY,
- maxY,
- startX,
- maxX);
- }
- }
- else
- {
- for (int x = 0; x < width; x++)
- {
- DenseMatrixUtils.Convolve2D4(
- in matrixY,
- in matrixX,
- source.PixelBuffer,
- ref vectorSpanRef,
- y,
- x,
- startY,
- maxY,
- startX,
- maxX);
- }
- }
-
- PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan);
- }
- });
-
- Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
+ DenseMatrixUtils.Convolve2D4(
+ in this.kernelY,
+ in this.kernelX,
+ this.sourcePixels,
+ ref spanRef,
+ y,
+ x,
+ this.bounds.Y,
+ this.maxY,
+ this.bounds.X,
+ this.maxX);
+ }
+ }
+
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
+ }
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
index 32bdf6bc5..33a8ab7d1 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
@@ -3,9 +3,9 @@
using System;
using System.Numerics;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
-using SixLabors.ImageSharp.Advanced.ParallelUtils;
+using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -59,95 +59,104 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source)
{
- using (Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()))
- {
- var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
- this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, this.Configuration);
- this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, this.Configuration);
- }
+ using Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size());
+
+ var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
+
+ // Horizontal convolution
+ var horizontalOperation = new RowIntervalOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha);
+ ParallelRowIterator.IterateRows(
+ this.Configuration,
+ interest,
+ in horizontalOperation);
+
+ // Vertical convolution
+ var verticalOperation = new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha);
+ ParallelRowIterator.IterateRows(
+ this.Configuration,
+ interest,
+ in verticalOperation);
}
///
- /// Applies the process to the specified portion of the specified at the specified location
- /// and with the specified size.
+ /// A implementing the convolution logic for .
///
- /// The target pixels to apply the process to.
- /// The source pixels. Cannot be null.
- ///
- /// The structure that specifies the portion of the image object to draw.
- ///
- /// The kernel operator.
- /// The
- private void ApplyConvolution(
- Buffer2D targetPixels,
- Buffer2D sourcePixels,
- Rectangle sourceRectangle,
- in DenseMatrix kernel,
- Configuration configuration)
+ private readonly struct RowIntervalOperation : IRowIntervalOperation
{
- DenseMatrix matrix = kernel;
- bool preserveAlpha = this.PreserveAlpha;
-
- int startY = sourceRectangle.Y;
- int endY = sourceRectangle.Bottom;
- int startX = sourceRectangle.X;
- int endX = sourceRectangle.Right;
- int maxY = endY - 1;
- int maxX = endX - 1;
-
- var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
- int width = workingRectangle.Width;
-
- ParallelHelper.IterateRowsWithTempBuffer(
- workingRectangle,
- configuration,
- (rows, vectorBuffer) =>
- {
- Span vectorSpan = vectorBuffer.Span;
- int length = vectorSpan.Length;
- ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
+ private readonly Rectangle bounds;
+ private readonly Buffer2D targetPixels;
+ private readonly Buffer2D sourcePixels;
+ private readonly DenseMatrix kernel;
+ private readonly Configuration configuration;
+ private readonly bool preserveAlpha;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperation(
+ Rectangle bounds,
+ Buffer2D targetPixels,
+ Buffer2D sourcePixels,
+ DenseMatrix kernel,
+ Configuration configuration,
+ bool preserveAlpha)
+ {
+ this.bounds = bounds;
+ this.targetPixels = targetPixels;
+ this.sourcePixels = sourcePixels;
+ this.kernel = kernel;
+ this.configuration = configuration;
+ this.preserveAlpha = preserveAlpha;
+ }
- for (int y = rows.Min; y < rows.Max; y++)
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows, Span span)
+ {
+ ref Vector4 spanRef = ref MemoryMarshal.GetReference(span);
+
+ int maxY = this.bounds.Bottom - 1;
+ int maxX = this.bounds.Right - 1;
+
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
+ PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
+
+ if (this.preserveAlpha)
+ {
+ for (int x = 0; x < this.bounds.Width; x++)
{
- Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
- PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan);
-
- if (preserveAlpha)
- {
- for (int x = 0; x < width; x++)
- {
- DenseMatrixUtils.Convolve3(
- in matrix,
- sourcePixels,
- ref vectorSpanRef,
- y,
- x,
- startY,
- maxY,
- startX,
- maxX);
- }
- }
- else
- {
- for (int x = 0; x < width; x++)
- {
- DenseMatrixUtils.Convolve4(
- in matrix,
- sourcePixels,
- ref vectorSpanRef,
- y,
- x,
- startY,
- maxY,
- startX,
- maxX);
- }
- }
-
- PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
+ DenseMatrixUtils.Convolve3(
+ in this.kernel,
+ this.sourcePixels,
+ ref spanRef,
+ y,
+ x,
+ this.bounds.Y,
+ maxY,
+ this.bounds.X,
+ maxX);
}
- });
+ }
+ else
+ {
+ for (int x = 0; x < this.bounds.Width; x++)
+ {
+ DenseMatrixUtils.Convolve4(
+ in this.kernel,
+ this.sourcePixels,
+ ref spanRef,
+ y,
+ x,
+ this.bounds.Y,
+ maxY,
+ this.bounds.X,
+ maxX);
+ }
+ }
+
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
+ }
+ }
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
index 285bcab27..542ee389b 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
@@ -3,9 +3,9 @@
using System;
using System.Numerics;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
-using SixLabors.ImageSharp.Advanced.ParallelUtils;
+using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -51,76 +51,99 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source)
{
- DenseMatrix matrix = this.KernelXY;
- bool preserveAlpha = this.PreserveAlpha;
+ using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size());
+
+ source.CopyTo(targetPixels);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
- int startY = interest.Y;
- int endY = interest.Bottom;
- int startX = interest.X;
- int endX = interest.Right;
- int maxY = endY - 1;
- int maxX = endX - 1;
+ var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha);
+ ParallelRowIterator.IterateRows(
+ this.Configuration,
+ interest,
+ in operation);
- using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()))
- {
- source.CopyTo(targetPixels);
+ Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
+ }
- var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
- int width = workingRectangle.Width;
+ ///
+ /// A implementing the convolution logic for .
+ ///
+ private readonly struct RowIntervalOperation : IRowIntervalOperation
+ {
+ private readonly Rectangle bounds;
+ private readonly int maxY;
+ private readonly int maxX;
+ private readonly Buffer2D targetPixels;
+ private readonly Buffer2D sourcePixels;
+ private readonly DenseMatrix kernel;
+ private readonly Configuration configuration;
+ private readonly bool preserveAlpha;
- ParallelHelper.IterateRowsWithTempBuffer(
- workingRectangle,
- this.Configuration,
- (rows, vectorBuffer) =>
- {
- Span vectorSpan = vectorBuffer.Span;
- int length = vectorSpan.Length;
- ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperation(
+ Rectangle bounds,
+ Buffer2D targetPixels,
+ Buffer2D sourcePixels,
+ DenseMatrix kernel,
+ Configuration configuration,
+ bool preserveAlpha)
+ {
+ this.bounds = bounds;
+ this.maxY = this.bounds.Bottom - 1;
+ this.maxX = this.bounds.Right - 1;
+ this.targetPixels = targetPixels;
+ this.sourcePixels = sourcePixels;
+ this.kernel = kernel;
+ this.configuration = configuration;
+ this.preserveAlpha = preserveAlpha;
+ }
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
- PixelOperations.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan);
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows, Span span)
+ {
+ ref Vector4 spanRef = ref MemoryMarshal.GetReference(span);
- if (preserveAlpha)
- {
- for (int x = 0; x < width; x++)
- {
- DenseMatrixUtils.Convolve3(
- in matrix,
- source.PixelBuffer,
- ref vectorSpanRef,
- y,
- x,
- startY,
- maxY,
- startX,
- maxX);
- }
- }
- else
- {
- for (int x = 0; x < width; x++)
- {
- DenseMatrixUtils.Convolve4(
- in matrix,
- source.PixelBuffer,
- ref vectorSpanRef,
- y,
- x,
- startY,
- maxY,
- startX,
- maxX);
- }
- }
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
+ PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
- PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan);
- }
- });
+ if (this.preserveAlpha)
+ {
+ for (int x = 0; x < this.bounds.Width; x++)
+ {
+ DenseMatrixUtils.Convolve3(
+ in this.kernel,
+ this.sourcePixels,
+ ref spanRef,
+ y,
+ x,
+ this.bounds.Y,
+ this.maxY,
+ this.bounds.X,
+ this.maxX);
+ }
+ }
+ else
+ {
+ for (int x = 0; x < this.bounds.Width; x++)
+ {
+ DenseMatrixUtils.Convolve4(
+ in this.kernel,
+ this.sourcePixels,
+ ref spanRef,
+ y,
+ x,
+ this.bounds.Y,
+ this.maxY,
+ this.bounds.X,
+ this.maxX);
+ }
+ }
- Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
+ }
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs
index 31c4fad79..c8c57fc29 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs
@@ -63,10 +63,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source)
{
- using (var processor = new Convolution2DProcessor(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle))
- {
- processor.Apply(source);
- }
+ using var processor = new Convolution2DProcessor(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle);
+
+ processor.Apply(source);
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
index c1897bed8..c4da1e4b0 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
@@ -1,12 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
-using SixLabors.ImageSharp.Advanced.ParallelUtils;
+using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
@@ -55,88 +53,79 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
DenseMatrix[] kernels = this.Kernels.Flatten();
- int startY = this.SourceRectangle.Y;
- int endY = this.SourceRectangle.Bottom;
- int startX = this.SourceRectangle.X;
- int endX = this.SourceRectangle.Right;
+ var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
- // Align start/end positions.
- int minX = Math.Max(0, startX);
- int maxX = Math.Min(source.Width, endX);
- int minY = Math.Max(0, startY);
- int maxY = Math.Min(source.Height, endY);
+ // We need a clean copy for each pass to start from
+ using ImageFrame cleanCopy = source.Clone();
- // we need a clean copy for each pass to start from
- using (ImageFrame cleanCopy = source.Clone())
+ using (var processor = new ConvolutionProcessor(this.Configuration, kernels[0], true, this.Source, interest))
{
- using (var processor = new ConvolutionProcessor(this.Configuration, kernels[0], true, this.Source, this.SourceRectangle))
- {
- processor.Apply(source);
- }
+ processor.Apply(source);
+ }
- if (kernels.Length == 1)
- {
- return;
- }
+ if (kernels.Length == 1)
+ {
+ return;
+ }
- int shiftY = startY;
- int shiftX = startX;
+ // Additional runs
+ for (int i = 1; i < kernels.Length; i++)
+ {
+ using ImageFrame pass = cleanCopy.Clone();
- // Reset offset if necessary.
- if (minX > 0)
+ using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, interest))
{
- shiftX = 0;
+ processor.Apply(pass);
}
- if (minY > 0)
- {
- shiftY = 0;
- }
+ var operation = new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, interest);
+ ParallelRowIterator.IterateRows(
+ this.Configuration,
+ interest,
+ in operation);
+ }
+ }
- var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
+ ///
+ /// A implementing the convolution logic for .
+ ///
+ private readonly struct RowIntervalOperation : IRowIntervalOperation
+ {
+ private readonly Buffer2D targetPixels;
+ private readonly Buffer2D passPixels;
+ private readonly int minX;
+ private readonly int maxX;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperation(
+ Buffer2D targetPixels,
+ Buffer2D passPixels,
+ Rectangle bounds)
+ {
+ this.targetPixels = targetPixels;
+ this.passPixels = passPixels;
+ this.minX = bounds.X;
+ this.maxX = bounds.Right;
+ }
- // Additional runs.
- // ReSharper disable once ForCanBeConvertedToForeach
- for (int i = 1; i < kernels.Length; i++)
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows)
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
{
- using (ImageFrame pass = cleanCopy.Clone())
+ ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(y));
+ ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(y));
+
+ for (int x = this.minX; x < this.maxX; x++)
{
- using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, this.SourceRectangle))
- {
- processor.Apply(pass);
- }
-
- Buffer2D passPixels = pass.PixelBuffer;
- Buffer2D targetPixels = source.PixelBuffer;
-
- ParallelHelper.IterateRows(
- workingRect,
- this.Configuration,
- rows =>
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- int offsetY = y - shiftY;
-
- ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY));
- ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY));
-
- for (int x = minX; x < maxX; x++)
- {
- int offsetX = x - shiftX;
-
- // Grab the max components of the two pixels
- ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX);
- ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX);
-
- var pixelValue = Vector4.Max(
- currentPassPixel.ToVector4(),
- currentTargetPixel.ToVector4());
-
- currentTargetPixel.FromVector4(pixelValue);
- }
- }
- });
+ // Grab the max components of the two pixels
+ ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, x);
+ ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, x);
+
+ var pixelValue = Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4());
+
+ currentTargetPixel.FromVector4(pixelValue);
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
index 3c1f82caa..3d0a7a714 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
@@ -44,10 +44,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source)
{
- using (var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
- {
- processor.Apply(source);
- }
+ using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle);
+
+ processor.Apply(source);
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
index f4f27a42d..506d34a3b 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
@@ -44,10 +44,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source)
{
- using (var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
- {
- processor.Apply(source);
- }
+ using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle);
+
+ processor.Apply(source);
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
index a8b9093e5..c1ce30cae 100644
--- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
+++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
@@ -2,9 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
-
+using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Advanced.ParallelUtils;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Drawing
@@ -99,18 +99,62 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
"Cannot draw image because the source image does not overlap the target image.");
}
- ParallelHelper.IterateRows(
- workingRect,
+ var operation = new RowIntervalOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity);
+ ParallelRowIterator.IterateRows(
configuration,
- rows =>
+ workingRect,
+ in operation);
+ }
+
+ ///
+ /// A implementing the draw logic for .
+ ///
+ private readonly struct RowIntervalOperation : IRowIntervalOperation
+ {
+ private readonly ImageFrame sourceFrame;
+ private readonly Image targetImage;
+ private readonly PixelBlender blender;
+ private readonly Configuration configuration;
+ private readonly int minX;
+ private readonly int width;
+ private readonly int locationY;
+ private readonly int targetX;
+ private readonly float opacity;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperation(
+ ImageFrame sourceFrame,
+ Image targetImage,
+ PixelBlender blender,
+ Configuration configuration,
+ int minX,
+ int width,
+ int locationY,
+ int targetX,
+ float opacity)
+ {
+ this.sourceFrame = sourceFrame;
+ this.targetImage = targetImage;
+ this.blender = blender;
+ this.configuration = configuration;
+ this.minX = minX;
+ this.width = width;
+ this.locationY = locationY;
+ this.targetX = targetX;
+ this.opacity = opacity;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows)
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
{
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span background = source.GetPixelRowSpan(y).Slice(minX, width);
- Span foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
- blender.Blend(configuration, background, background, foreground, this.Opacity);
- }
- });
+ Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width);
+ Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width);
+ this.blender.Blend(this.configuration, background, background, foreground, this.opacity);
+ }
+ }
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs
new file mode 100644
index 000000000..626ffd716
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Effects
+{
+ ///
+ /// An used by the row delegates for a given instance
+ ///
+ public interface IPixelRowDelegate
+ {
+ ///
+ /// Applies the current pixel row delegate to a target row of preprocessed pixels.
+ ///
+ /// The target row of pixels to process.
+ /// The initial horizontal and vertical offset for the input pixels to process.
+ void Invoke(Span span, Point offset);
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
index 472c07aa7..50c0a22d3 100644
--- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
@@ -5,9 +5,7 @@ using System;
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
-
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -45,122 +43,145 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
throw new ArgumentOutOfRangeException(nameof(brushSize));
}
- int startY = this.SourceRectangle.Y;
- int endY = this.SourceRectangle.Bottom;
- int startX = this.SourceRectangle.X;
- int endX = this.SourceRectangle.Right;
- int maxY = endY - 1;
- int maxX = endX - 1;
-
- int radius = brushSize >> 1;
- int levels = this.definition.Levels;
- int rowWidth = source.Width;
- int rectangleWidth = this.SourceRectangle.Width;
-
- Configuration configuration = this.Configuration;
-
using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size());
+
source.CopyTo(targetPixels);
- var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY);
- ParallelHelper.IterateRows(
- workingRect,
+ var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels);
+ ParallelRowIterator.IterateRows(
this.Configuration,
- (rows) =>
- {
- /* Allocate the two temporary Vector4 buffers, one for the source row and one for the target row.
- * The ParallelHelper.IterateRowsWithTempBuffers overload is not used in this case because
- * the two allocated buffers have a length equal to the width of the source image,
- * and not just equal to the width of the target rectangle to process.
- * Furthermore, there are two buffers being allocated in this case, so using that overload would
- * have still required the explicit allocation of the secondary buffer.
- * Similarly, one temporary float buffer is also allocated from the pool, and that is used
- * to create the target bins for all the color channels being processed.
- * This buffer is only rented once outside of the main processing loop, and its contents
- * are cleared for each loop iteration, to avoid the repeated allocation for each processed pixel. */
- using (IMemoryOwner sourceRowBuffer = configuration.MemoryAllocator.Allocate(rowWidth))
- using (IMemoryOwner targetRowBuffer = configuration.MemoryAllocator.Allocate(rowWidth))
- using (IMemoryOwner bins = configuration.MemoryAllocator.Allocate(levels * 4))
- {
- Span sourceRowVector4Span = sourceRowBuffer.Memory.Span;
- Span sourceRowAreaVector4Span = sourceRowVector4Span.Slice(startX, rectangleWidth);
+ this.SourceRectangle,
+ in operation);
- Span targetRowVector4Span = targetRowBuffer.Memory.Span;
- Span targetRowAreaVector4Span = targetRowVector4Span.Slice(startX, rectangleWidth);
-
- ref float binsRef = ref bins.GetReference();
- ref int intensityBinRef = ref Unsafe.As(ref binsRef);
- ref float redBinRef = ref Unsafe.Add(ref binsRef, levels);
- ref float blueBinRef = ref Unsafe.Add(ref redBinRef, levels);
- ref float greenBinRef = ref Unsafe.Add(ref blueBinRef, levels);
-
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span sourceRowPixelSpan = source.GetPixelRowSpan(y);
- Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(startX, rectangleWidth);
+ Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
+ }
- PixelOperations.Instance.ToVector4(configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span);
+ ///
+ /// A implementing the convolution logic for .
+ ///
+ private readonly struct RowIntervalOperation : IRowIntervalOperation
+ {
+ private readonly Rectangle bounds;
+ private readonly Buffer2D targetPixels;
+ private readonly ImageFrame source;
+ private readonly Configuration configuration;
+ private readonly int radius;
+ private readonly int levels;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperation(
+ Rectangle bounds,
+ Buffer2D targetPixels,
+ ImageFrame source,
+ Configuration configuration,
+ int radius,
+ int levels)
+ {
+ this.bounds = bounds;
+ this.targetPixels = targetPixels;
+ this.source = source;
+ this.configuration = configuration;
+ this.radius = radius;
+ this.levels = levels;
+ }
- for (int x = startX; x < endX; x++)
- {
- int maxIntensity = 0;
- int maxIndex = 0;
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows)
+ {
+ int maxY = this.bounds.Bottom - 1;
+ int maxX = this.bounds.Right - 1;
+
+ /* Allocate the two temporary Vector4 buffers, one for the source row and one for the target row.
+ * The ParallelHelper.IterateRowsWithTempBuffers overload is not used in this case because
+ * the two allocated buffers have a length equal to the width of the source image,
+ * and not just equal to the width of the target rectangle to process.
+ * Furthermore, there are two buffers being allocated in this case, so using that overload would
+ * have still required the explicit allocation of the secondary buffer.
+ * Similarly, one temporary float buffer is also allocated from the pool, and that is used
+ * to create the target bins for all the color channels being processed.
+ * This buffer is only rented once outside of the main processing loop, and its contents
+ * are cleared for each loop iteration, to avoid the repeated allocation for each processed pixel. */
+ using IMemoryOwner sourceRowBuffer = this.configuration.MemoryAllocator.Allocate(this.source.Width);
+ using IMemoryOwner targetRowBuffer = this.configuration.MemoryAllocator.Allocate(this.source.Width);
+ using IMemoryOwner bins = this.configuration.MemoryAllocator.Allocate(this.levels * 4);
+
+ Span sourceRowVector4Span = sourceRowBuffer.Memory.Span;
+ Span sourceRowAreaVector4Span = sourceRowVector4Span.Slice(this.bounds.X, this.bounds.Width);
+
+ Span targetRowVector4Span = targetRowBuffer.Memory.Span;
+ Span targetRowAreaVector4Span = targetRowVector4Span.Slice(this.bounds.X, this.bounds.Width);
+
+ ref float binsRef = ref bins.GetReference();
+ ref int intensityBinRef = ref Unsafe.As(ref binsRef);
+ ref float redBinRef = ref Unsafe.Add(ref binsRef, this.levels);
+ ref float blueBinRef = ref Unsafe.Add(ref redBinRef, this.levels);
+ ref float greenBinRef = ref Unsafe.Add(ref blueBinRef, this.levels);
+
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span