diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
index 4e6018e07a..dc73420f30 100644
--- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
+++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
@@ -1,95 +1,101 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Buffers;
-using System.Threading.Tasks;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Memory;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Memory;
-using SixLabors.Primitives;
-
-namespace SixLabors.ImageSharp.Processing.Processors.Drawing
-{
- ///
- /// Combines two images together by blending the pixels.
- ///
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Threading.Tasks;
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Memory;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Drawing
+{
+ ///
+ /// Combines two images together by blending the pixels.
+ ///
/// The pixel format of destination image.
- /// The pixel format os source image.
- internal class DrawImageProcessor : ImageProcessor
+ /// The pixel format of source image.
+ internal class DrawImageProcessor : ImageProcessor
where TPixelDst : struct, IPixel
- where TPixelSrc : struct, IPixel
+ where TPixelSrc : struct, IPixel
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The image to blend with the currently processing image.
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The image to blend with the currently processing image.
/// The location to draw the blended image.
/// The blending mode to use when drawing the image.
/// The Alpha blending mode to use when drawing the image.
- /// The opacity of the image to blend. Must be between 0 and 1.
- public DrawImageProcessor(Image image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity)
- {
- Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
-
- this.Image = image;
- this.Opacity = opacity;
- this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode);
- this.Location = location;
- }
-
- ///
- /// Gets the image to blend
- ///
- public Image Image { get; }
-
- ///
- /// Gets the opacity of the image to blend
- ///
- public float Opacity { get; }
-
- ///
- /// Gets the pixel blender
- ///
- public PixelBlender Blender { get; }
-
- ///
- /// Gets the location to draw the blended image
- ///
- public Point Location { get; }
-
- ///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- Image targetImage = this.Image;
- PixelBlender blender = this.Blender;
- int locationY = this.Location.Y;
-
- // Align start/end positions.
- Rectangle bounds = targetImage.Bounds();
-
- int minX = Math.Max(this.Location.X, sourceRectangle.X);
- int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width);
- int targetX = minX - this.Location.X;
-
- int minY = Math.Max(this.Location.Y, sourceRectangle.Y);
- int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
-
- int width = maxX - minX;
-
+ /// The opacity of the image to blend. Must be between 0 and 1.
+ public DrawImageProcessor(Image image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity)
+ {
+ Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
+
+ this.Image = image;
+ this.Opacity = opacity;
+ this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode);
+ this.Location = location;
+ }
+
+ ///
+ /// Gets the image to blend
+ ///
+ public Image Image { get; }
+
+ ///
+ /// Gets the opacity of the image to blend
+ ///
+ public float Opacity { get; }
+
+ ///
+ /// Gets the pixel blender
+ ///
+ public PixelBlender Blender { get; }
+
+ ///
+ /// Gets the location to draw the blended image
+ ///
+ public Point Location { get; }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ {
+ Image targetImage = this.Image;
+ PixelBlender blender = this.Blender;
+ int locationY = this.Location.Y;
+
+ // Align start/end positions.
+ Rectangle bounds = targetImage.Bounds();
+
+ int minX = Math.Max(this.Location.X, sourceRectangle.X);
+ int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width);
+ int targetX = minX - this.Location.X;
+
+ int minY = Math.Max(this.Location.Y, sourceRectangle.Y);
+ int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
+
+ int width = maxX - minX;
+
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator;
- ParallelFor.WithConfiguration(
- minY,
- maxY,
- configuration,
- y =>
- {
- Span background = source.GetPixelRowSpan(y).Slice(minX, width);
- Span foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
- blender.Blend(memoryAllocator, background, background, foreground, this.Opacity);
- });
- }
- }
+ var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
+
+ ParallelHelper.IterateRows(
+ workingRect,
+ configuration,
+ rows =>
+ {
+ 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(memoryAllocator, background, background, foreground, this.Opacity);
+ }
+ });
+ }
+ }
}
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
index 3285e75a7b..ed6c869511 100644
--- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
+++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
@@ -1,107 +1,116 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Buffers;
-using System.Threading.Tasks;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Memory;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Memory;
-using SixLabors.Primitives;
-
-namespace SixLabors.ImageSharp.Processing.Processors.Drawing
-{
- ///
- /// Using the brush as a source of pixels colors blends the brush color with source.
- ///
- /// The pixel format.
- internal class FillProcessor : ImageProcessor
- where TPixel : struct, IPixel
- {
- ///
- /// The brush.
- ///
- private readonly IBrush brush;
- private readonly GraphicsOptions options;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The brush to source pixel colors from.
- /// The options
- public FillProcessor(IBrush brush, GraphicsOptions options)
- {
- this.brush = brush;
- this.options = options;
- }
-
- ///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- int startX = sourceRectangle.X;
- int endX = sourceRectangle.Right;
- int startY = sourceRectangle.Y;
- int endY = sourceRectangle.Bottom;
-
- // 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);
-
- int width = maxX - minX;
-
- // If there's no reason for blending, then avoid it.
- if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush))
- {
- ParallelFor.WithConfiguration(
- minY,
- maxY,
- configuration,
- y =>
- {
- source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color);
- });
- }
- else
- {
- // Reset offset if necessary.
- if (minX > 0)
- {
- startX = 0;
- }
-
- if (minY > 0)
- {
- startY = 0;
- }
-
- using (IMemoryOwner amount = source.MemoryAllocator.Allocate(width))
- using (BrushApplicator applicator = this.brush.CreateApplicator(
- source,
- sourceRectangle,
- this.options))
- {
- amount.GetSpan().Fill(1f);
-
- ParallelFor.WithConfiguration(
- minY,
- maxY,
- configuration,
- y =>
- {
- int offsetY = y - startY;
- int offsetX = minX - startX;
-
- applicator.Apply(amount.GetSpan(), offsetX, offsetY);
- });
- }
- }
- }
-
- private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush)
- {
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Threading.Tasks;
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Memory;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Drawing
+{
+ ///
+ /// Using the brush as a source of pixels colors blends the brush color with source.
+ ///
+ /// The pixel format.
+ internal class FillProcessor : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// The brush.
+ ///
+ private readonly IBrush brush;
+ private readonly GraphicsOptions options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The brush to source pixel colors from.
+ /// The options
+ public FillProcessor(IBrush brush, GraphicsOptions options)
+ {
+ this.brush = brush;
+ this.options = options;
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ {
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+ int startY = sourceRectangle.Y;
+ int endY = sourceRectangle.Bottom;
+
+ // 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);
+
+ int width = maxX - minX;
+
+ var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
+
+ // If there's no reason for blending, then avoid it.
+ if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush))
+ {
+ ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4);
+
+ ParallelHelper.IterateRows(
+ workingRect,
+ parallelSettings,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color);
+ }
+ });
+ }
+ else
+ {
+ // Reset offset if necessary.
+ if (minX > 0)
+ {
+ startX = 0;
+ }
+
+ if (minY > 0)
+ {
+ startY = 0;
+ }
+
+ using (IMemoryOwner amount = source.MemoryAllocator.Allocate(width))
+ using (BrushApplicator applicator = this.brush.CreateApplicator(
+ source,
+ sourceRectangle,
+ this.options))
+ {
+ amount.GetSpan().Fill(1f);
+
+ ParallelHelper.IterateRows(
+ workingRect,
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ int offsetY = y - startY;
+ int offsetX = minX - startX;
+
+ applicator.Apply(amount.GetSpan(), offsetX, offsetY);
+ }
+ });
+ }
+ }
+ }
+
+ private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush)
+ {
solidBrush = this.brush as SolidBrush;
if (solidBrush == null)
@@ -109,7 +118,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
return false;
}
- return this.options.IsOpaqueColorWithoutBlending(solidBrush.Color);
- }
- }
+ return this.options.IsOpaqueColorWithoutBlending(solidBrush.Color);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Helpers/ParallelFor.cs b/src/ImageSharp/Common/Helpers/ParallelFor.cs
deleted file mode 100644
index 4c14bb6e3d..0000000000
--- a/src/ImageSharp/Common/Helpers/ParallelFor.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Buffers;
-using System.Threading.Tasks;
-using SixLabors.Memory;
-
-namespace SixLabors.ImageSharp
-{
- ///
- /// Utility methods for Parallel.For() execution. Use this instead of raw calls!
- ///
- internal static class ParallelFor
- {
- ///
- /// Helper method to execute Parallel.For using the settings in
- ///
- public static void WithConfiguration(int fromInclusive, int toExclusive, Configuration configuration, Action body)
- {
- Parallel.For(fromInclusive, toExclusive, configuration.GetParallelOptions(), body);
- }
-
- ///
- /// Helper method to execute Parallel.For with temporary worker buffer shared between executing tasks.
- /// The buffer is not guaranteed to be clean!
- ///
- /// The value type of the buffer
- /// The start index, inclusive.
- /// The end index, exclusive.
- /// The used for getting the and
- /// The length of the requested parallel buffer
- /// The delegate that is invoked once per iteration.
- public static void WithTemporaryBuffer(
- int fromInclusive,
- int toExclusive,
- Configuration configuration,
- int bufferLength,
- Action> body)
- where T : struct
- {
- MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
- ParallelOptions parallelOptions = configuration.GetParallelOptions();
-
- IMemoryOwner InitBuffer()
- {
- return memoryAllocator.Allocate(bufferLength);
- }
-
- void CleanUpBuffer(IMemoryOwner buffer)
- {
- buffer.Dispose();
- }
-
- IMemoryOwner BodyFunc(int i, ParallelLoopState state, IMemoryOwner buffer)
- {
- body(i, buffer);
- return buffer;
- }
-
- Parallel.For(fromInclusive, toExclusive, parallelOptions, InitBuffer, BodyFunc, CleanUpBuffer);
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs
new file mode 100644
index 0000000000..0b45719c38
--- /dev/null
+++ b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs
@@ -0,0 +1,71 @@
+// Copyright(c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Threading.Tasks;
+
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.ParallelUtils
+{
+ ///
+ /// Defines execution settings for methods in .
+ ///
+ internal readonly struct ParallelExecutionSettings
+ {
+ ///
+ /// Default value for .
+ ///
+ public const int DefaultMinimumPixelsProcessedPerTask = 4096;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ public ParallelExecutionSettings(
+ int maxDegreeOfParallelism,
+ int minimumPixelsProcessedPerTask,
+ MemoryAllocator memoryAllocator)
+ {
+ this.MaxDegreeOfParallelism = maxDegreeOfParallelism;
+ this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask;
+ this.MemoryAllocator = memoryAllocator;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator)
+ : this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator)
+ {
+ }
+
+ ///
+ /// Gets the MemoryAllocator
+ ///
+ public MemoryAllocator MemoryAllocator { get; }
+
+ ///
+ /// Gets the value used for initializing when using TPL.
+ ///
+ public int MaxDegreeOfParallelism { get; }
+
+ ///
+ /// Gets the minimum number of pixels being processed by a single task when parallelizing operations with TPL.
+ /// Launching tasks for pixel regions below this limit is not worth the overhead.
+ /// Initialized with by default,
+ /// the optimum value is operation specific. (The cheaper the operation, the larger the value is.)
+ ///
+ public int MinimumPixelsProcessedPerTask { get; }
+
+ ///
+ /// Creates a new instance of
+ /// having multiplied by
+ ///
+ public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier)
+ {
+ return new ParallelExecutionSettings(
+ this.MaxDegreeOfParallelism,
+ this.MinimumPixelsProcessedPerTask * multiplier,
+ this.MemoryAllocator);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs
new file mode 100644
index 0000000000..1d1734a863
--- /dev/null
+++ b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs
@@ -0,0 +1,146 @@
+// 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;
+using SixLabors.Memory;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.ParallelUtils
+{
+ ///
+ /// Utility methods for batched processing of pixel row intervals.
+ /// Parallel execution is optimized for image processing.
+ /// Use this instead of direct calls!
+ ///
+ internal static class ParallelHelper
+ {
+ ///
+ /// Get the default for a
+ ///
+ public static ParallelExecutionSettings GetParallelSettings(this Configuration configuration)
+ {
+ return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s.
+ ///
+ public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body)
+ {
+ ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings();
+
+ IterateRows(rectangle, parallelSettings, body);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s.
+ ///
+ public static void IterateRows(
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ Action body)
+ {
+ DebugGuard.MustBeGreaterThan(rectangle.Width, 0, nameof(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 };
+
+ Parallel.For(
+ 0,
+ numOfSteps,
+ parallelOptions,
+ i =>
+ {
+ int yMin = rectangle.Top + (i * verticalStep);
+ int yMax = Math.Min(yMin + verticalStep, rectangle.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.
+ ///
+ public static void IterateRowsWithTempBuffer(
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ Action> body)
+ where T : struct
+ {
+ 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 };
+
+ Parallel.For(
+ 0,
+ numOfSteps,
+ parallelOptions,
+ i =>
+ {
+ int yMin = rectangle.Top + (i * verticalStep);
+ 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.
+ ///
+ public static void IterateRowsWithTempBuffer(
+ Rectangle rectangle,
+ Configuration configuration,
+ Action> body)
+ where T : struct
+ {
+ IterateRowsWithTempBuffer(rectangle, configuration.GetParallelSettings(), body);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index 354701747a..576f7bf3d0 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -59,6 +59,7 @@ namespace SixLabors.ImageSharp
///
/// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms
/// configured with this instance.
+ /// Initialized with by default.
///
public int MaxDegreeOfParallelism
{
diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs
index 5f1aa5a38e..be1792ced1 100644
--- a/src/ImageSharp/ImageFrame{TPixel}.cs
+++ b/src/ImageSharp/ImageFrame{TPixel}.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@@ -288,20 +289,24 @@ namespace SixLabors.ImageSharp
var target = new ImageFrame(configuration, this.Width, this.Height, this.MetaData.DeepClone());
- ParallelFor.WithTemporaryBuffer(
- 0,
- this.Height,
+ ParallelHelper.IterateRowsWithTempBuffer(
+ this.Bounds(),
configuration,
- this.Width,
- (int y, IMemoryOwner tempRowBuffer) =>
- {
- Span sourceRow = this.GetPixelRowSpan(y);
- Span targetRow = target.GetPixelRowSpan(y);
- Span tempRowSpan = tempRowBuffer.GetSpan();
-
- PixelOperations.Instance.ToScaledVector4(sourceRow, tempRowSpan, sourceRow.Length);
- PixelOperations.Instance.PackFromScaledVector4(tempRowSpan, targetRow, targetRow.Length);
- });
+ (rows, tempRowBuffer) =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span sourceRow = this.GetPixelRowSpan(y);
+ Span targetRow = target.GetPixelRowSpan(y);
+ Span tempRowSpan = tempRowBuffer.Span;
+
+ PixelOperations.Instance.ToScaledVector4(sourceRow, tempRowSpan, sourceRow.Length);
+ PixelOperations.Instance.PackFromScaledVector4(
+ tempRowSpan,
+ targetRow,
+ targetRow.Length);
+ }
+ });
return target;
}
@@ -313,15 +318,16 @@ namespace SixLabors.ImageSharp
/// The value to initialize the bitmap with.
internal void Clear(ParallelOptions parallelOptions, TPixel value)
{
- Parallel.For(
- 0,
- this.Height,
- parallelOptions,
- y =>
- {
- Span targetRow = this.GetPixelRowSpan(y);
- targetRow.Fill(value);
- });
+ Span span = this.GetPixelSpan();
+
+ if (value.Equals(default))
+ {
+ span.Clear();
+ }
+ else
+ {
+ span.Fill(value);
+ }
}
///
diff --git a/src/ImageSharp/ImageSharp.csproj.DotSettings b/src/ImageSharp/ImageSharp.csproj.DotSettings
new file mode 100644
index 0000000000..8b2e1bcf07
--- /dev/null
+++ b/src/ImageSharp/ImageSharp.csproj.DotSettings
@@ -0,0 +1,3 @@
+
+ True
+ True
\ No newline at end of file
diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs
index 107457ae73..17ab6e2522 100644
--- a/src/ImageSharp/Memory/Buffer2DExtensions.cs
+++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs
@@ -96,15 +96,14 @@ namespace SixLabors.ImageSharp.Memory
/// The
/// The rectangle subarea
/// The
- public static BufferArea GetArea(this Buffer2D buffer, Rectangle rectangle)
+ public static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle)
where T : struct => new BufferArea(buffer, rectangle);
public static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height)
- where T : struct
- {
- var rectangle = new Rectangle(x, y, width, height);
- return new BufferArea(buffer, rectangle);
- }
+ where T : struct => new BufferArea(buffer, new Rectangle(x, y, width, height));
+
+ public static BufferArea GetAreaBetweenRows(this Buffer2D buffer, int minY, int maxY)
+ where T : struct => new BufferArea(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY));
///
/// Return a to the whole area of 'buffer'
@@ -114,5 +113,14 @@ namespace SixLabors.ImageSharp.Memory
/// The
public static BufferArea GetArea(this Buffer2D buffer)
where T : struct => new BufferArea(buffer);
+
+ ///
+ /// Gets a span for all the pixels in defined by
+ ///
+ public static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows)
+ where T : struct
+ {
+ return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Memory/RowInterval.cs b/src/ImageSharp/Memory/RowInterval.cs
new file mode 100644
index 0000000000..0750e0368c
--- /dev/null
+++ b/src/ImageSharp/Memory/RowInterval.cs
@@ -0,0 +1,45 @@
+// Copyright(c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Memory
+{
+ ///
+ /// Represents an interval of rows in a and/or
+ ///
+ internal readonly struct RowInterval
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ public RowInterval(int min, int max)
+ {
+ DebugGuard.MustBeLessThan(min, max, nameof(min));
+
+ this.Min = min;
+ this.Max = max;
+ }
+
+ ///
+ /// Gets the INCLUSIVE minimum
+ ///
+ public int Min { get; }
+
+ ///
+ /// Gets the EXCLUSIVE maximum
+ ///
+ public int Max { get; }
+
+ ///
+ /// Gets the difference ( - )
+ ///
+ public int Height => this.Max - this.Min;
+
+ ///
+ public override string ToString()
+ {
+ return $"RowInterval [{this.Min}->{this.Max}[";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
index c4f4266d98..60754b3bf2 100644
--- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
@@ -2,10 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Threading.Tasks;
+
using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization
@@ -56,7 +56,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
public TPixel LowerColor { get; set; }
///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ protected override void OnFrameApply(
+ ImageFrame source,
+ Rectangle sourceRectangle,
+ Configuration configuration)
{
float threshold = this.Threshold * 255F;
TPixel upper = this.UpperColor;
@@ -70,25 +73,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
- ParallelFor.WithConfiguration(
- startY,
- endY,
+ var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY);
+
+ ParallelHelper.IterateRows(
+ workingRect,
configuration,
- y =>
+ rows =>
{
- Span row = source.GetPixelRowSpan(y);
- Rgba32 rgba = default;
-
- for (int x = startX; x < endX; x++)
+ for (int y = rows.Min; y < rows.Max; y++)
{
- ref TPixel color = ref row[x];
- color.ToRgba32(ref rgba);
+ Span row = source.GetPixelRowSpan(y);
+ Rgba32 rgba = default;
+
+ for (int x = startX; x < endX; x++)
+ {
+ ref TPixel color = ref row[x];
+ color.ToRgba32(ref rgba);
- // Convert to grayscale using ITU-R Recommendation BT.709 if required
- float luminance = isAlphaOnly
- ? rgba.A
- : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);
- color = luminance >= threshold ? upper : lower;
+ // Convert to grayscale using ITU-R Recommendation BT.709 if required
+ float luminance = isAlphaOnly
+ ? rgba.A
+ : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);
+ color = luminance >= threshold ? upper : lower;
+ }
}
});
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
index b5a2725437..d2282ec0e1 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
@@ -3,12 +3,12 @@
using System;
using System.Numerics;
-using System.Threading.Tasks;
+
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
-using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
@@ -42,7 +42,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public DenseMatrix KernelY { get; }
///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ protected override void OnFrameApply(
+ ImageFrame source,
+ Rectangle sourceRectangle,
+ Configuration configuration)
{
int kernelYHeight = this.KernelY.Rows;
int kernelYWidth = this.KernelY.Columns;
@@ -58,71 +61,77 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
int maxY = endY - 1;
int maxX = endX - 1;
- using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height))
+ using (Buffer2D targetPixels =
+ configuration.MemoryAllocator.Allocate2D(source.Width, source.Height))
{
source.CopyTo(targetPixels);
- ParallelFor.WithConfiguration(
- startY,
- endY,
- configuration,
- y =>
- {
- Span sourceRow = source.GetPixelRowSpan(y);
- Span targetRow = targetPixels.GetRowSpan(y);
+ var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
- for (int x = startX; x < endX; x++)
+ ParallelHelper.IterateRows(
+ workingRectangle,
+ configuration,
+ rows =>
{
- float rX = 0;
- float gX = 0;
- float bX = 0;
- float rY = 0;
- float gY = 0;
- float bY = 0;
-
- // Apply each matrix multiplier to the color components for each pixel.
- for (int fy = 0; fy < kernelYHeight; fy++)
+ for (int y = rows.Min; y < rows.Max; y++)
{
- int fyr = fy - radiusY;
- int offsetY = y + fyr;
+ Span sourceRow = source.GetPixelRowSpan(y);
+ Span targetRow = targetPixels.GetRowSpan(y);
- offsetY = offsetY.Clamp(0, maxY);
- Span sourceOffsetRow = source.GetPixelRowSpan(offsetY);
-
- for (int fx = 0; fx < kernelXWidth; fx++)
+ for (int x = startX; x < endX; x++)
{
- int fxr = fx - radiusX;
- int offsetX = x + fxr;
-
- offsetX = offsetX.Clamp(0, maxX);
- Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply();
-
- if (fy < kernelXHeight)
+ float rX = 0;
+ float gX = 0;
+ float bX = 0;
+ float rY = 0;
+ float gY = 0;
+ float bY = 0;
+
+ // Apply each matrix multiplier to the color components for each pixel.
+ for (int fy = 0; fy < kernelYHeight; fy++)
{
- Vector4 kx = this.KernelX[fy, fx] * currentColor;
- rX += kx.X;
- gX += kx.Y;
- bX += kx.Z;
+ int fyr = fy - radiusY;
+ int offsetY = y + fyr;
+
+ offsetY = offsetY.Clamp(0, maxY);
+ Span sourceOffsetRow = source.GetPixelRowSpan(offsetY);
+
+ for (int fx = 0; fx < kernelXWidth; fx++)
+ {
+ int fxr = fx - radiusX;
+ int offsetX = x + fxr;
+
+ offsetX = offsetX.Clamp(0, maxX);
+ Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply();
+
+ if (fy < kernelXHeight)
+ {
+ Vector4 kx = this.KernelX[fy, fx] * currentColor;
+ rX += kx.X;
+ gX += kx.Y;
+ bX += kx.Z;
+ }
+
+ if (fx < kernelYWidth)
+ {
+ Vector4 ky = this.KernelY[fy, fx] * currentColor;
+ rY += ky.X;
+ gY += ky.Y;
+ bY += ky.Z;
+ }
+ }
}
- if (fx < kernelYWidth)
- {
- Vector4 ky = this.KernelY[fy, fx] * currentColor;
- rY += ky.X;
- gY += ky.Y;
- bY += ky.Z;
- }
+ float red = MathF.Sqrt((rX * rX) + (rY * rY));
+ float green = MathF.Sqrt((gX * gX) + (gY * gY));
+ float blue = MathF.Sqrt((bX * bX) + (bY * bY));
+
+ ref TPixel pixel = ref targetRow[x];
+ pixel.PackFromVector4(
+ new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply());
}
}
-
- float red = MathF.Sqrt((rX * rX) + (rY * rY));
- float green = MathF.Sqrt((gX * gX) + (gY * gY));
- float blue = MathF.Sqrt((bX * bX) + (bY * bY));
-
- ref TPixel pixel = ref targetRow[x];
- pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply());
- }
- });
+ });
Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
index 0808c07d03..e45bb3ab2e 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
@@ -6,6 +6,7 @@ using System.Numerics;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors;
@@ -82,43 +83,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
int maxY = endY - 1;
int maxX = endX - 1;
- ParallelFor.WithConfiguration(
- startY,
- endY,
- configuration,
- y =>
- {
- Span targetRow = targetPixels.GetRowSpan(y);
+ var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
- for (int x = startX; x < endX; x++)
+ ParallelHelper.IterateRows(
+ workingRectangle,
+ configuration,
+ rows =>
{
- Vector4 destination = default;
-
- // Apply each matrix multiplier to the color components for each pixel.
- for (int fy = 0; fy < kernelHeight; fy++)
+ for (int y = rows.Min; y < rows.Max; y++)
{
- int fyr = fy - radiusY;
- int offsetY = y + fyr;
+ Span targetRow = targetPixels.GetRowSpan(y);
- offsetY = offsetY.Clamp(0, maxY);
- Span row = sourcePixels.GetRowSpan(offsetY);
-
- for (int fx = 0; fx < kernelWidth; fx++)
+ for (int x = startX; x < endX; x++)
{
- int fxr = fx - radiusX;
- int offsetX = x + fxr;
+ Vector4 destination = default;
+
+ // Apply each matrix multiplier to the color components for each pixel.
+ for (int fy = 0; fy < kernelHeight; fy++)
+ {
+ int fyr = fy - radiusY;
+ int offsetY = y + fyr;
- offsetX = offsetX.Clamp(0, maxX);
+ offsetY = offsetY.Clamp(0, maxY);
+ Span row = sourcePixels.GetRowSpan(offsetY);
- Vector4 currentColor = row[offsetX].ToVector4().Premultiply();
- destination += kernel[fy, fx] * currentColor;
+ for (int fx = 0; fx < kernelWidth; fx++)
+ {
+ int fxr = fx - radiusX;
+ int offsetX = x + fxr;
+
+ offsetX = offsetX.Clamp(0, maxX);
+
+ Vector4 currentColor = row[offsetX].ToVector4().Premultiply();
+ destination += kernel[fy, fx] * currentColor;
+ }
+ }
+
+ ref TPixel pixel = ref targetRow[x];
+ pixel.PackFromVector4(destination.UnPremultiply());
}
}
-
- ref TPixel pixel = ref targetRow[x];
- pixel.PackFromVector4(destination.UnPremultiply());
- }
- });
+ });
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
index 31e638a0ad..bac9a86cfe 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
@@ -6,6 +6,7 @@ using System.Numerics;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors;
@@ -52,50 +53,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
source.CopyTo(targetPixels);
- ParallelFor.WithConfiguration(
- startY,
- endY,
- configuration,
- y =>
- {
- Span sourceRow = source.GetPixelRowSpan(y);
- Span targetRow = targetPixels.GetRowSpan(y);
-
- for (int x = startX; x < endX; x++)
- {
- float red = 0;
- float green = 0;
- float blue = 0;
-
- // Apply each matrix multiplier to the color components for each pixel.
- for (int fy = 0; fy < kernelLength; fy++)
- {
- int fyr = fy - radius;
- int offsetY = y + fyr;
-
- offsetY = offsetY.Clamp(0, maxY);
- Span sourceOffsetRow = source.GetPixelRowSpan(offsetY);
-
- for (int fx = 0; fx < kernelLength; fx++)
- {
- int fxr = fx - radius;
- int offsetX = x + fxr;
-
- offsetX = offsetX.Clamp(0, maxX);
-
- Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply();
- currentColor *= this.KernelXY[fy, fx];
-
- red += currentColor.X;
- green += currentColor.Y;
- blue += currentColor.Z;
- }
- }
-
- ref TPixel pixel = ref targetRow[x];
- pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply());
- }
- });
+ var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY);
+
+ ParallelHelper.IterateRows(
+ workingRect,
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span sourceRow = source.GetPixelRowSpan(y);
+ Span targetRow = targetPixels.GetRowSpan(y);
+
+ for (int x = startX; x < endX; x++)
+ {
+ float red = 0;
+ float green = 0;
+ float blue = 0;
+
+ // Apply each matrix multiplier to the color components for each pixel.
+ for (int fy = 0; fy < kernelLength; fy++)
+ {
+ int fyr = fy - radius;
+ int offsetY = y + fyr;
+
+ offsetY = offsetY.Clamp(0, maxY);
+ Span sourceOffsetRow = source.GetPixelRowSpan(offsetY);
+
+ for (int fx = 0; fx < kernelLength; fx++)
+ {
+ int fxr = fx - radius;
+ int offsetX = x + fxr;
+
+ offsetX = offsetX.Clamp(0, maxX);
+
+ Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply();
+ currentColor *= this.KernelXY[fy, fx];
+
+ red += currentColor.X;
+ green += currentColor.Y;
+ blue += currentColor.Z;
+ }
+ }
+
+ ref TPixel pixel = ref targetRow[x];
+ pixel.PackFromVector4(
+ new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply());
+ }
+ }
+ });
Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs
index 316de422f5..ebf9c8dec2 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs
@@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors.Filters;
@@ -124,6 +125,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
shiftY = 0;
}
+ var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
+
// Additional runs.
// ReSharper disable once ForCanBeConvertedToForeach
for (int i = 1; i < kernels.Length; i++)
@@ -135,30 +138,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Buffer2D passPixels = pass.PixelBuffer;
Buffer2D targetPixels = source.PixelBuffer;
- ParallelFor.WithConfiguration(
- minY,
- maxY,
+ ParallelHelper.IterateRows(
+ workingRect,
configuration,
- y =>
+ rows =>
{
- int offsetY = y - shiftY;
+ 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));
+ 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;
+ 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);
+ // 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());
+ var pixelValue = Vector4.Max(
+ currentPassPixel.ToVector4(),
+ currentTargetPixel.ToVector4());
- currentTargetPixel.PackFromVector4(pixelValue);
+ currentTargetPixel.PackFromVector4(pixelValue);
+ }
}
});
}
diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs
index 59898e9fc1..6ad4dcba97 100644
--- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs
@@ -3,11 +3,11 @@
using System;
using System.Numerics;
-using System.Threading.Tasks;
+
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Effects
@@ -49,7 +49,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
public int BrushSize { get; }
///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ protected override void OnFrameApply(
+ ImageFrame source,
+ Rectangle sourceRectangle,
+ Configuration configuration)
{
if (this.BrushSize <= 0 || this.BrushSize > source.Height || this.BrushSize > source.Width)
{
@@ -70,69 +73,74 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
{
source.CopyTo(targetPixels);
- ParallelFor.WithConfiguration(
- startY,
- maxY,
+ var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY);
+ ParallelHelper.IterateRows(
+ workingRect,
configuration,
- y =>
- {
- Span sourceRow = source.GetPixelRowSpan(y);
- Span targetRow = targetPixels.GetRowSpan(y);
-
- for (int x = startX; x < endX; x++)
+ rows =>
{
- int maxIntensity = 0;
- int maxIndex = 0;
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span sourceRow = source.GetPixelRowSpan(y);
+ Span targetRow = targetPixels.GetRowSpan(y);
- int[] intensityBin = new int[levels];
- float[] redBin = new float[levels];
- float[] blueBin = new float[levels];
- float[] greenBin = new float[levels];
+ for (int x = startX; x < endX; x++)
+ {
+ int maxIntensity = 0;
+ int maxIndex = 0;
- for (int fy = 0; fy <= radius; fy++)
- {
- int fyr = fy - radius;
- int offsetY = y + fyr;
+ int[] intensityBin = new int[levels];
+ float[] redBin = new float[levels];
+ float[] blueBin = new float[levels];
+ float[] greenBin = new float[levels];
- offsetY = offsetY.Clamp(0, maxY);
+ for (int fy = 0; fy <= radius; fy++)
+ {
+ int fyr = fy - radius;
+ int offsetY = y + fyr;
- Span sourceOffsetRow = source.GetPixelRowSpan(offsetY);
+ offsetY = offsetY.Clamp(0, maxY);
- for (int fx = 0; fx <= radius; fx++)
- {
- int fxr = fx - radius;
- int offsetX = x + fxr;
- offsetX = offsetX.Clamp(0, maxX);
+ Span sourceOffsetRow = source.GetPixelRowSpan(offsetY);
- var vector = sourceOffsetRow[offsetX].ToVector4();
+ for (int fx = 0; fx <= radius; fx++)
+ {
+ int fxr = fx - radius;
+ int offsetX = x + fxr;
+ offsetX = offsetX.Clamp(0, maxX);
- float sourceRed = vector.X;
- float sourceBlue = vector.Z;
- float sourceGreen = vector.Y;
+ var vector = sourceOffsetRow[offsetX].ToVector4();
- int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1));
+ float sourceRed = vector.X;
+ float sourceBlue = vector.Z;
+ float sourceGreen = vector.Y;
- intensityBin[currentIntensity]++;
- blueBin[currentIntensity] += sourceBlue;
- greenBin[currentIntensity] += sourceGreen;
- redBin[currentIntensity] += sourceRed;
+ int currentIntensity = (int)MathF.Round(
+ (sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1));
- if (intensityBin[currentIntensity] > maxIntensity)
- {
- maxIntensity = intensityBin[currentIntensity];
- maxIndex = currentIntensity;
- }
- }
+ intensityBin[currentIntensity]++;
+ blueBin[currentIntensity] += sourceBlue;
+ greenBin[currentIntensity] += sourceGreen;
+ redBin[currentIntensity] += sourceRed;
- float red = MathF.Abs(redBin[maxIndex] / maxIntensity);
- float green = MathF.Abs(greenBin[maxIndex] / maxIntensity);
- float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity);
+ if (intensityBin[currentIntensity] > maxIntensity)
+ {
+ maxIntensity = intensityBin[currentIntensity];
+ maxIndex = currentIntensity;
+ }
+ }
- ref TPixel pixel = ref targetRow[x];
- pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W));
+ float red = MathF.Abs(redBin[maxIndex] / maxIntensity);
+ float green = MathF.Abs(greenBin[maxIndex] / maxIntensity);
+ float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity);
+
+ ref TPixel pixel = ref targetRow[x];
+ pixel.PackFromVector4(
+ new Vector4(red, green, blue, sourceRow[x].ToVector4().W));
+ }
+ }
}
- }
- });
+ });
Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs
index 6244d8bf76..e20b42eb7c 100644
--- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs
@@ -5,6 +5,7 @@ using System;
using System.Numerics;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@@ -35,25 +36,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
{
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
- int startY = interest.Y;
- int endY = interest.Bottom;
- int startX = interest.X;
- int endX = interest.Right;
+
Matrix4x4 matrix = this.Matrix;
- ParallelFor.WithConfiguration(
- startY,
- endY,
+ ParallelHelper.IterateRows(
+ interest,
configuration,
- y =>
+ rows =>
{
- Span row = source.GetPixelRowSpan(y);
-
- for (int x = startX; x < endX; x++)
+ for (int y = rows.Min; y < rows.Max; y++)
{
- ref TPixel pixel = ref row[x];
- var vector = Vector4.Transform(pixel.ToVector4(), matrix);
- pixel.PackFromVector4(vector);
+ Span row = source.GetPixelRowSpan(y);
+
+ for (int x = interest.X; x < interest.Right; x++)
+ {
+ ref TPixel pixel = ref row[x];
+ var vector = Vector4.Transform(pixel.ToVector4(), matrix);
+ pixel.PackFromVector4(vector);
+ }
}
});
}
diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs
index ecbeebeb06..4adddd1536 100644
--- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs
@@ -6,6 +6,7 @@ using System.Buffers;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@@ -67,6 +68,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
int width = maxX - minX;
+ var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
+
using (IMemoryOwner colors = source.MemoryAllocator.Allocate(width))
using (IMemoryOwner amount = source.MemoryAllocator.Allocate(width))
{
@@ -74,25 +77,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
Span colorSpan = colors.GetSpan();
Span amountSpan = amount.GetSpan();
- // TODO: Use Span.Fill?
- for (int i = 0; i < width; i++)
- {
- colorSpan[i] = this.Color;
- amountSpan[i] = this.GraphicsOptions.BlendPercentage;
- }
+ colorSpan.Fill(this.Color);
+ amountSpan.Fill(this.GraphicsOptions.BlendPercentage);
PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.GraphicsOptions);
- ParallelFor.WithConfiguration(
- minY,
- maxY,
+
+ ParallelHelper.IterateRows(
+ workingRect,
configuration,
- y =>
- {
- Span destination = source.GetPixelRowSpan(y - startY).Slice(minX - startX, width);
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span destination =
+ source.GetPixelRowSpan(y - startY).Slice(minX - startX, width);
- // This switched color & destination in the 2nd and 3rd places because we are applying the target color under the current one
- blender.Blend(source.MemoryAllocator, destination, colors.GetSpan(), destination, amount.GetSpan());
- });
+ // This switched color & destination in the 2nd and 3rd places because we are applying the target color under the current one
+ blender.Blend(
+ source.MemoryAllocator,
+ destination,
+ colors.GetSpan(),
+ destination,
+ amount.GetSpan());
+ }
+ });
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
index eb91fec043..93d6edff19 100644
--- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
@@ -7,6 +7,7 @@ using System.Numerics;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.Memory;
@@ -84,6 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
{
+ // TODO: can we simplify the rectangle calculation?
int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
@@ -113,36 +115,43 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
}
int width = maxX - minX;
+ int offsetX = minX - startX;
+
+ var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
+
using (IMemoryOwner rowColors = source.MemoryAllocator.Allocate(width))
{
- // Be careful! Do not capture rowColorsSpan in the lambda below!
- Span rowColorsSpan = rowColors.GetSpan();
-
- for (int i = 0; i < width; i++)
- {
- rowColorsSpan[i] = glowColor;
- }
+ rowColors.GetSpan().Fill(glowColor);
- ParallelFor.WithTemporaryBuffer(
- minY,
- maxY,
+ ParallelHelper.IterateRowsWithTempBuffer(
+ workingRect,
configuration,
- width,
- (y, amounts) =>
- {
- Span amountsSpan = amounts.GetSpan();
- int offsetY = y - startY;
- int offsetX = minX - startX;
- for (int i = 0; i < width; i++)
+ (rows, amounts) =>
{
- float distance = Vector2.Distance(center, new Vector2(i + offsetX, offsetY));
- amountsSpan[i] = (this.GraphicsOptions.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1);
- }
-
- Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width);
-
- this.blender.Blend(source.MemoryAllocator, destination, destination, rowColors.GetSpan(), amountsSpan);
- });
+ Span amountsSpan = amounts.Span;
+
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ int offsetY = y - startY;
+
+ for (int i = 0; i < width; i++)
+ {
+ float distance = Vector2.Distance(center, new Vector2(i + offsetX, offsetY));
+ amountsSpan[i] =
+ (this.GraphicsOptions.BlendPercentage * (1 - (.95F * (distance / maxDistance))))
+ .Clamp(0, 1);
+ }
+
+ Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width);
+
+ this.blender.Blend(
+ source.MemoryAllocator,
+ destination,
+ destination,
+ rowColors.GetSpan(),
+ amountsSpan);
+ }
+ });
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
index 63780df476..52dade4eff 100644
--- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
@@ -7,6 +7,7 @@ using System.Numerics;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.Memory;
@@ -115,43 +116,43 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
}
int width = maxX - minX;
+ int offsetX = minX - startX;
+
+ var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
+
using (IMemoryOwner rowColors = source.MemoryAllocator.Allocate(width))
{
- // Be careful! Do not capture rowColorsSpan in the lambda below!
- Span rowColorsSpan = rowColors.GetSpan();
-
- for (int i = 0; i < width; i++)
- {
- rowColorsSpan[i] = vignetteColor;
- }
+ rowColors.GetSpan().Fill(vignetteColor);
- ParallelFor.WithTemporaryBuffer(
- minY,
- maxY,
+ ParallelHelper.IterateRowsWithTempBuffer(
+ workingRect,
configuration,
- width,
- (y, amounts) =>
+ (rows, amounts) =>
{
- Span amountsSpan = amounts.GetSpan();
- int offsetY = y - startY;
- int offsetX = minX - startX;
- for (int i = 0; i < width; i++)
+ Span amountsSpan = amounts.Span;
+
+ for (int y = rows.Min; y < rows.Max; y++)
{
- float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY));
- amountsSpan[i] =
- (this.GraphicsOptions.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(
- 0,
- 1);
+ int offsetY = y - startY;
+
+ for (int i = 0; i < width; i++)
+ {
+ float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY));
+ amountsSpan[i] =
+ (this.GraphicsOptions.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(
+ 0,
+ 1);
+ }
+
+ Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width);
+
+ this.blender.Blend(
+ source.MemoryAllocator,
+ destination,
+ destination,
+ rowColors.GetSpan(),
+ amountsSpan);
}
-
- Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width);
-
- this.blender.Blend(
- source.MemoryAllocator,
- destination,
- destination,
- rowColors.GetSpan(),
- amountsSpan);
});
}
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
index a7e1589259..3469161e6d 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
@@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@@ -78,23 +79,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (this.Sampler is NearestNeighborResampler)
{
- ParallelFor.WithConfiguration(
- 0,
- height,
+ ParallelHelper.IterateRows(
+ targetBounds,
configuration,
- y =>
- {
- Span destRow = destination.GetPixelRowSpan(y);
-
- for (int x = 0; x < width; x++)
+ rows =>
{
- var point = Point.Transform(new Point(x, y), matrix);
- if (sourceBounds.Contains(point.X, point.Y))
+ for (int y = rows.Min; y < rows.Max; y++)
{
- destRow[x] = source[point.X, point.Y];
+ Span destRow = destination.GetPixelRowSpan(y);
+
+ for (int x = 0; x < width; x++)
+ {
+ var point = Point.Transform(new Point(x, y), matrix);
+ if (sourceBounds.Contains(point.X, point.Y))
+ {
+ destRow[x] = source[point.X, point.Y];
+ }
+ }
}
- }
- });
+ });
return;
}
@@ -116,86 +119,107 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
using (Buffer2D yBuffer = memoryAllocator.Allocate2D(yLength, height))
using (Buffer2D xBuffer = memoryAllocator.Allocate2D(xLength, height))
{
- ParallelFor.WithConfiguration(
- 0,
- height,
+ ParallelHelper.IterateRows(
+ targetBounds,
configuration,
- y =>
+ rows =>
{
- ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
- ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y));
- ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y));
-
- for (int x = 0; x < width; x++)
+ for (int y = rows.Min; y < rows.Max; y++)
{
- // Use the single precision position to calculate correct bounding pixels
- // otherwise we get rogue pixels outside of the bounds.
- var point = Vector2.Transform(new Vector2(x, y), matrix);
-
- // Clamp sampling pixel radial extents to the source image edges
- Vector2 maxXY = point + radius;
- Vector2 minXY = point - radius;
-
- // max, maxY, minX, minY
- var extents = new Vector4(
- MathF.Floor(maxXY.X + .5F),
- MathF.Floor(maxXY.Y + .5F),
- MathF.Ceiling(minXY.X - .5F),
- MathF.Ceiling(minXY.Y - .5F));
-
- int right = (int)extents.X;
- int bottom = (int)extents.Y;
- int left = (int)extents.Z;
- int top = (int)extents.W;
-
- extents = Vector4.Clamp(extents, Vector4.Zero, maxSource);
-
- int maxX = (int)extents.X;
- int maxY = (int)extents.Y;
- int minX = (int)extents.Z;
- int minY = (int)extents.W;
-
- if (minX == maxX || minY == maxY)
- {
- continue;
- }
+ ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
+ ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y));
+ ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y));
- // It appears these have to be calculated on-the-fly.
- // Precalulating transformed weights would require prior knowledge of every transformed pixel location
- // since they can be at sub-pixel positions on both axis.
- // I've optimized where I can but am always open to suggestions.
- if (yScale > 1 && xScale > 1)
- {
- CalculateWeightsDown(top, bottom, minY, maxY, point.Y, sampler, yScale, ref ySpanRef, yLength);
- CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, ref xSpanRef, xLength);
- }
- else
+ for (int x = 0; x < width; x++)
{
- CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef);
- CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef);
- }
+ // Use the single precision position to calculate correct bounding pixels
+ // otherwise we get rogue pixels outside of the bounds.
+ var point = Vector2.Transform(new Vector2(x, y), matrix);
+
+ // Clamp sampling pixel radial extents to the source image edges
+ Vector2 maxXY = point + radius;
+ Vector2 minXY = point - radius;
+
+ // max, maxY, minX, minY
+ var extents = new Vector4(
+ MathF.Floor(maxXY.X + .5F),
+ MathF.Floor(maxXY.Y + .5F),
+ MathF.Ceiling(minXY.X - .5F),
+ MathF.Ceiling(minXY.Y - .5F));
+
+ int right = (int)extents.X;
+ int bottom = (int)extents.Y;
+ int left = (int)extents.Z;
+ int top = (int)extents.W;
+
+ extents = Vector4.Clamp(extents, Vector4.Zero, maxSource);
+
+ int maxX = (int)extents.X;
+ int maxY = (int)extents.Y;
+ int minX = (int)extents.Z;
+ int minY = (int)extents.W;
+
+ if (minX == maxX || minY == maxY)
+ {
+ continue;
+ }
- // Now multiply the results against the offsets
- Vector4 sum = Vector4.Zero;
- for (int yy = 0, j = minY; j <= maxY; j++, yy++)
- {
- float yWeight = Unsafe.Add(ref ySpanRef, yy);
+ // It appears these have to be calculated on-the-fly.
+ // Precalculating transformed weights would require prior knowledge of every transformed pixel location
+ // since they can be at sub-pixel positions on both axis.
+ // I've optimized where I can but am always open to suggestions.
+ if (yScale > 1 && xScale > 1)
+ {
+ CalculateWeightsDown(
+ top,
+ bottom,
+ minY,
+ maxY,
+ point.Y,
+ sampler,
+ yScale,
+ ref ySpanRef,
+ yLength);
+
+ CalculateWeightsDown(
+ left,
+ right,
+ minX,
+ maxX,
+ point.X,
+ sampler,
+ xScale,
+ ref xSpanRef,
+ xLength);
+ }
+ else
+ {
+ CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef);
+ CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef);
+ }
- for (int xx = 0, i = minX; i <= maxX; i++, xx++)
+ // Now multiply the results against the offsets
+ Vector4 sum = Vector4.Zero;
+ for (int yy = 0, j = minY; j <= maxY; j++, yy++)
{
- float xWeight = Unsafe.Add(ref xSpanRef, xx);
- var vector = source[i, j].ToVector4();
+ float yWeight = Unsafe.Add(ref ySpanRef, yy);
+
+ for (int xx = 0, i = minX; i <= maxX; i++, xx++)
+ {
+ float xWeight = Unsafe.Add(ref xSpanRef, xx);
+ var vector = source[i, j].ToVector4();
- // Values are first premultiplied to prevent darkening of edge pixels
- Vector4 multiplied = vector.Premultiply();
- sum += multiplied * xWeight * yWeight;
+ // Values are first premultiplied to prevent darkening of edge pixels
+ Vector4 multiplied = vector.Premultiply();
+ sum += multiplied * xWeight * yWeight;
+ }
}
- }
- ref TPixel dest = ref Unsafe.Add(ref destRowRef, x);
+ ref TPixel dest = ref Unsafe.Add(ref destRowRef, x);
- // Reverse the premultiplication
- dest.PackFromVector4(sum.UnPremultiply());
+ // Reverse the premultiplication
+ dest.PackFromVector4(sum.UnPremultiply());
+ }
}
});
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
index df1ac32746..8e6a826fd3 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
@@ -4,8 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@@ -53,21 +53,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return;
}
- int minY = Math.Max(this.CropRectangle.Y, sourceRectangle.Y);
- int maxY = Math.Min(this.CropRectangle.Bottom, sourceRectangle.Bottom);
- int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X);
- int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right);
+ var rect = Rectangle.Intersect(this.CropRectangle, sourceRectangle);
- ParallelFor.WithConfiguration(
- minY,
- maxY,
- configuration,
- y =>
- {
- Span sourceRow = source.GetPixelRowSpan(y).Slice(minX);
- Span targetRow = destination.GetPixelRowSpan(y - minY);
- sourceRow.Slice(0, maxX - minX).CopyTo(targetRow);
- });
+ // Copying is cheap, we should process more pixels per task:
+ ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4);
+
+ ParallelHelper.IterateRows(
+ rect,
+ parallelSettings,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span sourceRow = source.GetPixelRowSpan(y).Slice(rect.Left);
+ Span targetRow = destination.GetPixelRowSpan(y - rect.Top);
+ sourceRow.Slice(0, rect.Width).CopyTo(targetRow);
+ }
+ });
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs
index cea6df391f..c6f5e9d7b8 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs
@@ -2,9 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Buffers;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@@ -55,27 +57,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private void FlipX(ImageFrame source, Configuration configuration)
{
int height = source.Height;
- int halfHeight = (int)Math.Ceiling(source.Height * .5F);
- using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Size()))
+ using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width))
{
- ParallelFor.WithConfiguration(
- 0,
- halfHeight,
- configuration,
- y =>
- {
- int newY = height - y - 1;
- Span sourceRow = source.GetPixelRowSpan(y);
- Span altSourceRow = source.GetPixelRowSpan(newY);
- Span targetRow = targetPixels.GetRowSpan(y);
- Span altTargetRow = targetPixels.GetRowSpan(newY);
-
- sourceRow.CopyTo(altTargetRow);
- altSourceRow.CopyTo(targetRow);
- });
+ Span temp = tempBuffer.Memory.Span;
- Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
+ for (int yTop = 0; yTop < height / 2; yTop++)
+ {
+ int yBottom = height - yTop - 1;
+ Span topRow = source.GetPixelRowSpan(yBottom);
+ Span bottomRow = source.GetPixelRowSpan(yTop);
+ topRow.CopyTo(temp);
+ bottomRow.CopyTo(topRow);
+ temp.CopyTo(bottomRow);
+ }
}
}
@@ -86,31 +81,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The configuration.
private void FlipY(ImageFrame source, Configuration configuration)
{
- int width = source.Width;
- int height = source.Height;
- int halfWidth = (int)Math.Ceiling(width * .5F);
-
- using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Size()))
- {
- ParallelFor.WithConfiguration(
- 0,
- height,
- configuration,
- y =>
+ ParallelHelper.IterateRows(
+ source.Bounds(),
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
{
- Span sourceRow = source.GetPixelRowSpan(y);
- Span targetRow = targetPixels.GetRowSpan(y);
-
- for (int x = 0; x < halfWidth; x++)
- {
- int newX = width - x - 1;
- targetRow[x] = sourceRow[newX];
- targetRow[newX] = sourceRow[x];
- }
- });
-
- Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
- }
+ source.GetPixelRowSpan(y).Reverse();
+ }
+ });
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
index 15816cb4dd..b03dec032f 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
@@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@@ -75,28 +76,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (this.Sampler is NearestNeighborResampler)
{
- ParallelFor.WithConfiguration(
- 0,
- height,
+ ParallelHelper.IterateRows(
+ targetBounds,
configuration,
- y =>
- {
- Span destRow = destination.GetPixelRowSpan(y);
-
- for (int x = 0; x < width; x++)
+ rows =>
{
- var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix);
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span destRow = destination.GetPixelRowSpan(y);
- float z = MathF.Max(v3.Z, Epsilon);
- int px = (int)MathF.Round(v3.X / z);
- int py = (int)MathF.Round(v3.Y / z);
+ for (int x = 0; x < width; x++)
+ {
+ var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix);
- if (sourceBounds.Contains(px, py))
- {
- destRow[x] = source[px, py];
+ float z = MathF.Max(v3.Z, Epsilon);
+ int px = (int)MathF.Round(v3.X / z);
+ int py = (int)MathF.Round(v3.Y / z);
+
+ if (sourceBounds.Contains(px, py))
+ {
+ destRow[x] = source[px, py];
+ }
+ }
}
- }
- });
+ });
return;
}
@@ -121,92 +124,113 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
using (Buffer2D yBuffer = memoryAllocator.Allocate2D(yLength, height))
using (Buffer2D xBuffer = memoryAllocator.Allocate2D(xLength, height))
{
- ParallelFor.WithConfiguration(
- 0,
- height,
+ ParallelHelper.IterateRows(
+ targetBounds,
configuration,
- y =>
- {
- ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
- ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y));
- ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y));
-
- for (int x = 0; x < width; x++)
+ rows =>
{
- // Use the single precision position to calculate correct bounding pixels
- // otherwise we get rogue pixels outside of the bounds.
- var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix);
- float z = MathF.Max(v3.Z, Epsilon);
-
- // Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable:
- Vector4 point = new Vector4(v3.X, v3.Y, 0, 0) / z;
-
- // Clamp sampling pixel radial extents to the source image edges
- Vector4 maxXY = point + radius;
- Vector4 minXY = point - radius;
-
- // max, maxY, minX, minY
- var extents = new Vector4(
- MathF.Floor(maxXY.X + .5F),
- MathF.Floor(maxXY.Y + .5F),
- MathF.Ceiling(minXY.X - .5F),
- MathF.Ceiling(minXY.Y - .5F));
-
- int right = (int)extents.X;
- int bottom = (int)extents.Y;
- int left = (int)extents.Z;
- int top = (int)extents.W;
-
- extents = Vector4.Clamp(extents, Vector4.Zero, maxSource);
-
- int maxX = (int)extents.X;
- int maxY = (int)extents.Y;
- int minX = (int)extents.Z;
- int minY = (int)extents.W;
-
- if (minX == maxX || minY == maxY)
- {
- continue;
- }
-
- // It appears these have to be calculated on-the-fly.
- // Precalulating transformed weights would require prior knowledge of every transformed pixel location
- // since they can be at sub-pixel positions on both axis.
- // I've optimized where I can but am always open to suggestions.
- if (yScale > 1 && xScale > 1)
- {
- CalculateWeightsDown(top, bottom, minY, maxY, point.Y, sampler, yScale, ref ySpanRef, yLength);
- CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, ref xSpanRef, xLength);
- }
- else
+ for (int y = rows.Min; y < rows.Max; y++)
{
- CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef);
- CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef);
- }
+ ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
+ ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y));
+ ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y));
- // Now multiply the results against the offsets
- Vector4 sum = Vector4.Zero;
- for (int yy = 0, j = minY; j <= maxY; j++, yy++)
- {
- float yWeight = Unsafe.Add(ref ySpanRef, yy);
-
- for (int xx = 0, i = minX; i <= maxX; i++, xx++)
+ for (int x = 0; x < width; x++)
{
- float xWeight = Unsafe.Add(ref xSpanRef, xx);
- var vector = source[i, j].ToVector4();
-
- // Values are first premultiplied to prevent darkening of edge pixels
- Vector4 multiplied = vector.Premultiply();
- sum += multiplied * xWeight * yWeight;
+ // Use the single precision position to calculate correct bounding pixels
+ // otherwise we get rogue pixels outside of the bounds.
+ var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix);
+ float z = MathF.Max(v3.Z, Epsilon);
+
+ // Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable:
+ Vector4 point = new Vector4(v3.X, v3.Y, 0, 0) / z;
+
+ // Clamp sampling pixel radial extents to the source image edges
+ Vector4 maxXY = point + radius;
+ Vector4 minXY = point - radius;
+
+ // max, maxY, minX, minY
+ var extents = new Vector4(
+ MathF.Floor(maxXY.X + .5F),
+ MathF.Floor(maxXY.Y + .5F),
+ MathF.Ceiling(minXY.X - .5F),
+ MathF.Ceiling(minXY.Y - .5F));
+
+ int right = (int)extents.X;
+ int bottom = (int)extents.Y;
+ int left = (int)extents.Z;
+ int top = (int)extents.W;
+
+ extents = Vector4.Clamp(extents, Vector4.Zero, maxSource);
+
+ int maxX = (int)extents.X;
+ int maxY = (int)extents.Y;
+ int minX = (int)extents.Z;
+ int minY = (int)extents.W;
+
+ if (minX == maxX || minY == maxY)
+ {
+ continue;
+ }
+
+ // It appears these have to be calculated on-the-fly.
+ // Precalulating transformed weights would require prior knowledge of every transformed pixel location
+ // since they can be at sub-pixel positions on both axis.
+ // I've optimized where I can but am always open to suggestions.
+ if (yScale > 1 && xScale > 1)
+ {
+ CalculateWeightsDown(
+ top,
+ bottom,
+ minY,
+ maxY,
+ point.Y,
+ sampler,
+ yScale,
+ ref ySpanRef,
+ yLength);
+
+ CalculateWeightsDown(
+ left,
+ right,
+ minX,
+ maxX,
+ point.X,
+ sampler,
+ xScale,
+ ref xSpanRef,
+ xLength);
+ }
+ else
+ {
+ CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef);
+ CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef);
+ }
+
+ // Now multiply the results against the offsets
+ Vector4 sum = Vector4.Zero;
+ for (int yy = 0, j = minY; j <= maxY; j++, yy++)
+ {
+ float yWeight = Unsafe.Add(ref ySpanRef, yy);
+
+ for (int xx = 0, i = minX; i <= maxX; i++, xx++)
+ {
+ float xWeight = Unsafe.Add(ref xSpanRef, xx);
+ var vector = source[i, j].ToVector4();
+
+ // Values are first premultiplied to prevent darkening of edge pixels
+ Vector4 multiplied = vector.Premultiply();
+ sum += multiplied * xWeight * yWeight;
+ }
+ }
+
+ ref TPixel dest = ref Unsafe.Add(ref destRowRef, x);
+
+ // Reverse the premultiplication
+ dest.PackFromVector4(sum.UnPremultiply());
}
}
-
- ref TPixel dest = ref Unsafe.Add(ref destRowRef, x);
-
- // Reverse the premultiplication
- dest.PackFromVector4(sum.UnPremultiply());
- }
- });
+ });
}
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
index b1c0632c66..76abc64996 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
@@ -11,6 +11,7 @@ using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@@ -267,26 +268,31 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (this.Sampler is NearestNeighborResampler)
{
+ var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
+
// Scaling factors
float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height;
- ParallelFor.WithConfiguration(
- minY,
- maxY,
+ ParallelHelper.IterateRows(
+ workingRect,
configuration,
- y =>
- {
- // Y coordinates of source points
- Span sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY));
- Span targetRow = destination.GetPixelRowSpan(y);
-
- for (int x = minX; x < maxX; x++)
+ rows =>
{
- // X coordinates of source points
- targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)];
- }
- });
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ // Y coordinates of source points
+ Span sourceRow =
+ source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY));
+ Span targetRow = destination.GetPixelRowSpan(y);
+
+ for (int x = minX; x < maxX; x++)
+ {
+ // X coordinates of source points
+ targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)];
+ }
+ }
+ });
return;
}
@@ -300,72 +306,88 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
firstPassPixels.MemorySource.Clear();
- ParallelFor.WithTemporaryBuffer(
- 0,
- sourceRectangle.Bottom,
+ var processColsRect = new Rectangle(0, 0, source.Width, sourceRectangle.Bottom);
+
+ ParallelHelper.IterateRowsWithTempBuffer(
+ processColsRect,
configuration,
- source.Width,
- (int y, IMemoryOwner tempRowBuffer) =>
+ (rows, tempRowBuffer) =>
{
- ref Vector4 firstPassRow = ref MemoryMarshal.GetReference(firstPassPixels.GetRowSpan(y));
- Span sourceRow = source.GetPixelRowSpan(y);
- Span tempRowSpan = tempRowBuffer.GetSpan();
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ ref Vector4 firstPassRow =
+ ref MemoryMarshal.GetReference(firstPassPixels.GetRowSpan(y));
+ Span sourceRow = source.GetPixelRowSpan(y);
+ Span tempRowSpan = tempRowBuffer.Span;
- PixelOperations.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length);
+ PixelOperations.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length);
- if (this.Compand)
- {
- for (int x = minX; x < maxX; x++)
+ if (this.Compand)
{
- WeightsWindow window = this.horizontalWeights.Weights[x - startX];
- Unsafe.Add(ref firstPassRow, x) = window.ComputeExpandedWeightedRowSum(tempRowSpan, sourceX);
+ for (int x = minX; x < maxX; x++)
+ {
+ WeightsWindow window = this.horizontalWeights.Weights[x - startX];
+ Unsafe.Add(ref firstPassRow, x) =
+ window.ComputeExpandedWeightedRowSum(tempRowSpan, sourceX);
+ }
}
- }
- else
- {
- for (int x = minX; x < maxX; x++)
+ else
{
- WeightsWindow window = this.horizontalWeights.Weights[x - startX];
- Unsafe.Add(ref firstPassRow, x) = window.ComputeWeightedRowSum(tempRowSpan, sourceX);
+ for (int x = minX; x < maxX; x++)
+ {
+ WeightsWindow window = this.horizontalWeights.Weights[x - startX];
+ Unsafe.Add(ref firstPassRow, x) =
+ window.ComputeWeightedRowSum(tempRowSpan, sourceX);
+ }
}
}
});
+ var processRowsRect = Rectangle.FromLTRB(0, minY, width, maxY);
+
// Now process the rows.
- ParallelFor.WithConfiguration(
- minY,
- maxY,
+ ParallelHelper.IterateRows(
+ processRowsRect,
configuration,
- y =>
- {
- // Ensure offsets are normalized for cropping and padding.
- WeightsWindow window = this.verticalWeights.Weights[y - startY];
- ref TPixel targetRow = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
-
- if (this.Compand)
- {
- for (int x = 0; x < width; x++)
- {
- // Destination color components
- Vector4 destinationVector = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY);
- destinationVector = destinationVector.Compress();
-
- ref TPixel pixel = ref Unsafe.Add(ref targetRow, x);
- pixel.PackFromVector4(destinationVector);
- }
- }
- else
+ rows =>
{
- for (int x = 0; x < width; x++)
+ for (int y = rows.Min; y < rows.Max; y++)
{
- // Destination color components
- Vector4 destinationVector = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY);
+ // Ensure offsets are normalized for cropping and padding.
+ WeightsWindow window = this.verticalWeights.Weights[y - startY];
+ ref TPixel targetRow = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
- ref TPixel pixel = ref Unsafe.Add(ref targetRow, x);
- pixel.PackFromVector4(destinationVector);
+ if (this.Compand)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ // Destination color components
+ Vector4 destinationVector = window.ComputeWeightedColumnSum(
+ firstPassPixels,
+ x,
+ sourceY);
+ destinationVector = destinationVector.Compress();
+
+ ref TPixel pixel = ref Unsafe.Add(ref targetRow, x);
+ pixel.PackFromVector4(destinationVector);
+ }
+ }
+ else
+ {
+ for (int x = 0; x < width; x++)
+ {
+ // Destination color components
+ Vector4 destinationVector = window.ComputeWeightedColumnSum(
+ firstPassPixels,
+ x,
+ sourceY);
+
+ ref TPixel pixel = ref Unsafe.Add(ref targetRow, x);
+ pixel.PackFromVector4(destinationVector);
+ }
+ }
}
- }
- });
+ });
}
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
index 93c847d598..2ad626755c 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
@@ -5,6 +5,7 @@ using System;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.Primitives;
@@ -147,25 +148,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int height = source.Height;
Rectangle destinationBounds = destination.Bounds();
- ParallelFor.WithConfiguration(
- 0,
- height,
+ ParallelHelper.IterateRows(
+ source.Bounds(),
configuration,
- y =>
- {
- Span sourceRow = source.GetPixelRowSpan(y);
- for (int x = 0; x < width; x++)
+ rows =>
{
- int newX = height - y - 1;
- newX = height - newX - 1;
- int newY = width - x - 1;
-
- if (destinationBounds.Contains(newX, newY))
+ for (int y = rows.Min; y < rows.Max; y++)
{
- destination[newX, newY] = sourceRow[x];
+ Span sourceRow = source.GetPixelRowSpan(y);
+ for (int x = 0; x < width; x++)
+ {
+ int newX = height - y - 1;
+ newX = height - newX - 1;
+ int newY = width - x - 1;
+
+ if (destinationBounds.Contains(newX, newY))
+ {
+ destination[newX, newY] = sourceRow[x];
+ }
+ }
}
- }
- });
+ });
}
///
@@ -179,20 +182,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int width = source.Width;
int height = source.Height;
- ParallelFor.WithConfiguration(
- 0,
- height,
+ ParallelHelper.IterateRows(
+ source.Bounds(),
configuration,
- y =>
- {
- Span sourceRow = source.GetPixelRowSpan(y);
- Span targetRow = destination.GetPixelRowSpan(height - y - 1);
-
- for (int x = 0; x < width; x++)
+ rows =>
{
- targetRow[width - x - 1] = sourceRow[x];
- }
- });
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span sourceRow = source.GetPixelRowSpan(y);
+ Span targetRow = destination.GetPixelRowSpan(height - y - 1);
+
+ for (int x = 0; x < width; x++)
+ {
+ targetRow[width - x - 1] = sourceRow[x];
+ }
+ }
+ });
}
///
@@ -207,22 +212,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int height = source.Height;
Rectangle destinationBounds = destination.Bounds();
- ParallelFor.WithConfiguration(
- 0,
- height,
+ ParallelHelper.IterateRows(
+ source.Bounds(),
configuration,
- y =>
- {
- Span sourceRow = source.GetPixelRowSpan(y);
- int newX = height - y - 1;
- for (int x = 0; x < width; x++)
+ rows =>
{
- if (destinationBounds.Contains(newX, x))
+ for (int y = rows.Min; y < rows.Max; y++)
{
- destination[newX, x] = sourceRow[x];
+ Span sourceRow = source.GetPixelRowSpan(y);
+ int newX = height - y - 1;
+ for (int x = 0; x < width; x++)
+ {
+ // TODO: Optimize this:
+ if (destinationBounds.Contains(newX, x))
+ {
+ destination[newX, x] = sourceRow[x];
+ }
+ }
}
- }
- });
+ });
}
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Codecs/CopyPixels.cs b/tests/ImageSharp.Benchmarks/Codecs/CopyPixels.cs
deleted file mode 100644
index d55c231a73..0000000000
--- a/tests/ImageSharp.Benchmarks/Codecs/CopyPixels.cs
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Threading.Tasks;
-
-using BenchmarkDotNet.Attributes;
-
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Memory;
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Benchmarks.Codecs
-{
- public class CopyPixels : BenchmarkBase
- {
- [Benchmark(Baseline = true, Description = "PixelAccessor Copy by indexer")]
- public Rgba32 CopyByPixelAccesor()
- {
- using (var source = new Image(1024, 768))
- using (var target = new Image(1024, 768))
- {
- Buffer2D sourcePixels = source.GetRootFramePixelBuffer();
- Buffer2D targetPixels = target.GetRootFramePixelBuffer();
- ParallelFor.WithConfiguration(
- 0,
- source.Height,
- Configuration.Default,
- y =>
- {
- for (int x = 0; x < source.Width; x++)
- {
- targetPixels[x, y] = sourcePixels[x, y];
- }
- });
-
- return targetPixels[0, 0];
- }
- }
-
- [Benchmark(Description = "PixelAccessor Copy by Span")]
- public Rgba32 CopyByPixelAccesorSpan()
- {
- using (var source = new Image(1024, 768))
- using (var target = new Image(1024, 768))
- {
- Buffer2D sourcePixels = source.GetRootFramePixelBuffer();
- Buffer2D targetPixels = target.GetRootFramePixelBuffer();
- ParallelFor.WithConfiguration(
- 0,
- source.Height,
- Configuration.Default,
- y =>
- {
- Span sourceRow = sourcePixels.GetRowSpan(y);
- Span targetRow = targetPixels.GetRowSpan(y);
-
- for (int x = 0; x < source.Width; x++)
- {
- targetRow[x] = sourceRow[x];
- }
- });
-
- return targetPixels[0, 0];
- }
- }
-
- [Benchmark(Description = "Copy by indexer")]
- public Rgba32 Copy()
- {
- using (var source = new Image(1024, 768))
- using (var target = new Image(1024, 768))
- {
- ParallelFor.WithConfiguration(
- 0,
- source.Height,
- Configuration.Default,
- y =>
- {
- for (int x = 0; x < source.Width; x++)
- {
- target[x, y] = source[x, y];
- }
- });
-
- return target[0, 0];
- }
- }
-
- [Benchmark(Description = "Copy by Span")]
- public Rgba32 CopySpan()
- {
- using (var source = new Image(1024, 768))
- using (var target = new Image(1024, 768))
- {
- ParallelFor.WithConfiguration(
- 0,
- source.Height,
- Configuration.Default,
- y =>
- {
- Span sourceRow = source.Frames.RootFrame.GetPixelRowSpan(y);
- Span targetRow = target.Frames.RootFrame.GetPixelRowSpan(y);
-
- for (int x = 0; x < source.Width; x++)
- {
- targetRow[x] = sourceRow[x];
- }
- });
-
- return target[0, 0];
- }
- }
- }
-}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs
index fe1d4221d1..ff2e57b974 100644
--- a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs
+++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.ImageSharp.Processing.Processors.Overlays;
@@ -112,26 +113,29 @@ namespace SixLabors.ImageSharp.Benchmarks
Buffer2D sourcePixels = source.PixelBuffer;
rowColors.GetSpan().Fill(glowColor);
- ParallelFor.WithConfiguration(
- minY,
- maxY,
+ var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
+ ParallelHelper.IterateRows(
+ workingRect,
configuration,
- y =>
+ rows =>
{
- int offsetY = y - startY;
-
- for (int x = minX; x < maxX; x++)
+ for (int y = rows.Min; y < rows.Max; y++)
{
- int offsetX = x - startX;
- float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY));
- Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4();
- TPixel packed = default(TPixel);
- packed.PackFromVector4(
- PremultipliedLerp(
- sourceColor,
- glowColor.ToVector4(),
- 1 - (.95F * (distance / maxDistance))));
- sourcePixels[offsetX, offsetY] = packed;
+ int offsetY = y - startY;
+
+ for (int x = minX; x < maxX; x++)
+ {
+ int offsetX = x - startX;
+ float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY));
+ Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4();
+ TPixel packed = default(TPixel);
+ packed.PackFromVector4(
+ PremultipliedLerp(
+ sourceColor,
+ glowColor.ToVector4(),
+ 1 - (.95F * (distance / maxDistance))));
+ sourcePixels[offsetX, offsetY] = packed;
+ }
}
});
}
diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs
index 93715c586e..b310c7afc6 100644
--- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs
@@ -54,172 +54,225 @@ namespace SixLabors.ImageSharp.Tests.Drawing
[Fact]
public void ImageShouldBeFloodFilledWithPercent10()
{
- this.Test("Percent10", Rgba32.Blue, Brushes.Percent10(Rgba32.HotPink, Rgba32.LimeGreen),
+ this.Test(
+ "Percent10",
+ Rgba32.Blue,
+ Brushes.Percent10(Rgba32.HotPink, Rgba32.LimeGreen),
new[,]
- {
- { Rgba32.HotPink , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink , Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen}
- });
+ {
+ { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithPercent10Transparent()
{
- Test("Percent10_Transparent", Rgba32.Blue, Brushes.Percent10(Rgba32.HotPink),
- new Rgba32[,] {
- { Rgba32.HotPink , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink , Rgba32.Blue},
- { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue}
- });
+ this.Test(
+ "Percent10_Transparent",
+ Rgba32.Blue,
+ Brushes.Percent10(Rgba32.HotPink),
+ new Rgba32[,]
+ {
+ { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithPercent20()
{
- Test("Percent20", Rgba32.Blue, Brushes.Percent20(Rgba32.HotPink, Rgba32.LimeGreen),
- new Rgba32[,] {
- { Rgba32.HotPink , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink , Rgba32.LimeGreen},
- { Rgba32.HotPink , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink , Rgba32.LimeGreen}
- });
+ this.Test(
+ "Percent20",
+ Rgba32.Blue,
+ Brushes.Percent20(Rgba32.HotPink, Rgba32.LimeGreen),
+ new Rgba32[,]
+ {
+ { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen },
+ { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithPercent20_transparent()
{
- Test("Percent20_Transparent", Rgba32.Blue, Brushes.Percent20(Rgba32.HotPink),
- new Rgba32[,] {
- { Rgba32.HotPink , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink , Rgba32.Blue},
- { Rgba32.HotPink , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink , Rgba32.Blue}
- });
+ this.Test(
+ "Percent20_Transparent",
+ Rgba32.Blue,
+ Brushes.Percent20(Rgba32.HotPink),
+ new Rgba32[,]
+ {
+ { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue },
+ { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithHorizontal()
{
- Test("Horizontal", Rgba32.Blue, Brushes.Horizontal(Rgba32.HotPink, Rgba32.LimeGreen),
- new Rgba32[,] {
- { Rgba32.LimeGreen , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink , Rgba32.HotPink},
- { Rgba32.LimeGreen , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen , Rgba32.LimeGreen}
- });
+ this.Test(
+ "Horizontal",
+ Rgba32.Blue,
+ Brushes.Horizontal(Rgba32.HotPink, Rgba32.LimeGreen),
+ new Rgba32[,]
+ {
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithHorizontal_transparent()
{
- Test("Horizontal_Transparent", Rgba32.Blue, Brushes.Horizontal(Rgba32.HotPink),
- new Rgba32[,] {
- { Rgba32.Blue , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink , Rgba32.HotPink},
- { Rgba32.Blue , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue , Rgba32.Blue}
- });
+ this.Test(
+ "Horizontal_Transparent",
+ Rgba32.Blue,
+ Brushes.Horizontal(Rgba32.HotPink),
+ new Rgba32[,]
+ {
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }
+ });
}
-
-
[Fact]
public void ImageShouldBeFloodFilledWithMin()
{
- Test("Min", Rgba32.Blue, Brushes.Min(Rgba32.HotPink, Rgba32.LimeGreen),
- new Rgba32[,] {
- { Rgba32.LimeGreen , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen , Rgba32.LimeGreen},
- { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink , Rgba32.HotPink}
- });
+ this.Test(
+ "Min",
+ Rgba32.Blue,
+ Brushes.Min(Rgba32.HotPink, Rgba32.LimeGreen),
+ new Rgba32[,]
+ {
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithMin_transparent()
{
- Test("Min_Transparent", Rgba32.Blue, Brushes.Min(Rgba32.HotPink),
- new Rgba32[,] {
- { Rgba32.Blue , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue , Rgba32.Blue},
- { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink , Rgba32.HotPink},
- });
+ this.Test(
+ "Min_Transparent",
+ Rgba32.Blue,
+ Brushes.Min(Rgba32.HotPink),
+ new Rgba32[,]
+ {
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink },
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithVertical()
{
- Test("Vertical", Rgba32.Blue, Brushes.Vertical(Rgba32.HotPink, Rgba32.LimeGreen),
- new Rgba32[,] {
- { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen}
- });
+ this.Test(
+ "Vertical",
+ Rgba32.Blue,
+ Brushes.Vertical(Rgba32.HotPink, Rgba32.LimeGreen),
+ new Rgba32[,]
+ {
+ { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithVertical_transparent()
{
- Test("Vertical_Transparent", Rgba32.Blue, Brushes.Vertical(Rgba32.HotPink),
- new Rgba32[,] {
- { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue}
- });
+ this.Test(
+ "Vertical_Transparent",
+ Rgba32.Blue,
+ Brushes.Vertical(Rgba32.HotPink),
+ new Rgba32[,]
+ {
+ { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithForwardDiagonal()
{
- Test("ForwardDiagonal", Rgba32.Blue, Brushes.ForwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen),
- new Rgba32[,] {
- { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink},
- { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen}
- });
+ this.Test(
+ "ForwardDiagonal",
+ Rgba32.Blue,
+ Brushes.ForwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen),
+ new Rgba32[,]
+ {
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithForwardDiagonal_transparent()
{
- Test("ForwardDiagonal_Transparent", Rgba32.Blue, Brushes.ForwardDiagonal(Rgba32.HotPink),
- new Rgba32[,] {
- { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink},
- { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue}
- });
+ this.Test(
+ "ForwardDiagonal_Transparent",
+ Rgba32.Blue,
+ Brushes.ForwardDiagonal(Rgba32.HotPink),
+ new Rgba32[,]
+ {
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithBackwardDiagonal()
{
- Test("BackwardDiagonal", Rgba32.Blue, Brushes.BackwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen),
- new Rgba32[,] {
- { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen},
- { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink}
- });
+ this.Test(
+ "BackwardDiagonal",
+ Rgba32.Blue,
+ Brushes.BackwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen),
+ new Rgba32[,]
+ {
+ { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen },
+ { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink }
+ });
}
[Fact]
public void ImageShouldBeFloodFilledWithBackwardDiagonal_transparent()
{
- Test("BackwardDiagonal_Transparent", Rgba32.Blue, Brushes.BackwardDiagonal(Rgba32.HotPink),
- new Rgba32[,] {
- { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue},
- { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink}
- });
+ this.Test(
+ "BackwardDiagonal_Transparent",
+ Rgba32.Blue,
+ Brushes.BackwardDiagonal(Rgba32.HotPink),
+ new Rgba32[,]
+ {
+ { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue },
+ { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink }
+ });
}
}
-}
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
index e86d41f574..32f723e72a 100644
--- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
@@ -1,20 +1,21 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
+
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Primitives;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
+using SixLabors.Primitives;
using SixLabors.Shapes;
+
using Xunit;
+
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing
{
- using System;
-
- using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
- using SixLabors.Primitives;
-
[GroupOutput("Drawing")]
public class FillSolidBrushTests
{
@@ -55,7 +56,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing
[Theory]
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")]
[WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")]
- public void WhenColorIsOpaque_OverridePreviousColor(TestImageProvider