diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs
new file mode 100644
index 000000000..a0fd2aaf2
--- /dev/null
+++ b/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Advanced.ParallelUtils
+{
+ ///
+ /// Defines the contract for.
+ ///
+ public interface IRowAction
+ {
+ ///
+ /// Invokes the method passing the row interval.
+ ///
+ /// The row interval.
+ void Invoke(in RowInterval rows);
+ }
+
+ internal readonly struct WrappingRowAction : IRowAction
+ where T : struct, IRowAction
+ {
+ private readonly T action;
+
+ public WrappingRowAction(ref T action)
+ {
+ this.action = action;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows)
+ {
+ this.action.Invoke(in rows);
+ }
+ }
+}
diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
index ecefadb08..6f9b98d1d 100644
--- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
+++ b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
@@ -28,19 +28,86 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
- IterateRows(rectangle, parallelSettings, body);
+ IterateRows(rectangle, in parallelSettings, body);
}
///
/// Iterate through the rows of a rectangle in optimized batches defined by -s.
///
+ /// The type of row action to perform.
/// The .
- /// The .
+ /// The to get the parallel settings from.
/// The method body defining the iteration logic on a single .
- public static void IterateRows(
+ public static void IterateRowsFast(Rectangle rectangle, Configuration configuration, ref T body)
+ where T : struct, IRowAction
+ {
+ var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+
+ IterateRowsFast(rectangle, in parallelSettings, ref body);
+ }
+
+ internal static void IterateRowsFast(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
- Action body)
+ ref T body)
+ where T : struct, IRowAction
+ {
+ 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.Invoke(in rows);
+ return;
+ }
+
+ int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
+
+ var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
+
+ int top = rectangle.Top;
+ int bottom = rectangle.Bottom;
+
+ var rowAction = new WrappingRowAction(ref body);
+
+ 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);
+
+ rowAction.Invoke(in rows);
+ });
+ }
+
+ ///
+ /// 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);
@@ -72,6 +139,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom);
var rows = new RowInterval(yMin, yMax);
+
body(rows);
});
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
index a286e8fa2..ec78ba171 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
@@ -2,8 +2,10 @@
// 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.Transforms
@@ -50,18 +52,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration)
.MultiplyMinimumPixelsPerTask(4);
- ParallelHelper.IterateRows(
+ // ParallelHelper.IterateRows(
+ // bounds,
+ // parallelSettings,
+ // rows =>
+ // {
+ // for (int y = rows.Min; y < rows.Max; y++)
+ // {
+ // Span sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left);
+ // Span targetRow = destination.GetPixelRowSpan(y - bounds.Top);
+ // sourceRow.Slice(0, bounds.Width).CopyTo(targetRow);
+ // }
+ // });
+ var rowAction = new RowAction(ref bounds, source, destination);
+
+ ParallelHelper.IterateRowsFast(
bounds,
- parallelSettings,
- rows =>
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left);
- Span targetRow = destination.GetPixelRowSpan(y - bounds.Top);
- sourceRow.Slice(0, bounds.Width).CopyTo(targetRow);
- }
- });
+ in parallelSettings,
+ ref rowAction);
+ }
+
+ private readonly struct RowAction : IRowAction
+ {
+ private readonly Rectangle bounds;
+ private readonly ImageFrame source;
+ private readonly ImageFrame destination;
+
+ public RowAction(ref Rectangle bounds, ImageFrame source, ImageFrame destination)
+ {
+ this.bounds = bounds;
+ this.source = source;
+ this.destination = destination;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows)
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left);
+ Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top);
+ sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow);
+ }
+ }
}
}
}