Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System;

using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
using System.Buffers;

using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;

Expand Down Expand Up @@ -45,17 +45,18 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)

int width = maxX - minX;

var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
Rectangle workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);

IBrush brush = this.definition.Brush;
GraphicsOptions options = this.definition.Options;

// If there's no reason for blending, then avoid it.
if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush))
{
ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4);
ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration)
.MultiplyMinimumPixelsPerTask(4);

TPixel colorPixel = solidBrush.Color.ToPixel<TPixel>();
var colorPixel = solidBrush.Color.ToPixel<TPixel>();

ParallelHelper.IterateRows(
workingRect,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Threading.Tasks;

using SixLabors.Memory;

namespace SixLabors.ImageSharp.ParallelUtils
namespace SixLabors.ImageSharp.Advanced.ParallelUtils
{
/// <summary>
/// Defines execution settings for methods in <see cref="ParallelHelper"/>.
/// </summary>
internal readonly struct ParallelExecutionSettings
public readonly struct ParallelExecutionSettings
{
/// <summary>
/// Default value for <see cref="MinimumPixelsProcessedPerTask"/>.
Expand All @@ -20,11 +21,24 @@ internal readonly struct ParallelExecutionSettings
/// <summary>
/// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct.
/// </summary>
/// <param name="maxDegreeOfParallelism">The value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.</param>
/// <param name="minimumPixelsProcessedPerTask">The value for <see cref="MinimumPixelsProcessedPerTask"/>.</param>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/>.</param>
public ParallelExecutionSettings(
int maxDegreeOfParallelism,
int minimumPixelsProcessedPerTask,
MemoryAllocator memoryAllocator)
{
// Shall be compatible with ParallelOptions.MaxDegreeOfParallelism:
// https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions.maxdegreeofparallelism
if (maxDegreeOfParallelism == 0 || maxDegreeOfParallelism < -1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

{
throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism));
}

Guard.MustBeGreaterThan(minimumPixelsProcessedPerTask, 0, nameof(minimumPixelsProcessedPerTask));
Guard.NotNull(memoryAllocator, nameof(memoryAllocator));

this.MaxDegreeOfParallelism = maxDegreeOfParallelism;
this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask;
this.MemoryAllocator = memoryAllocator;
Expand All @@ -33,13 +47,15 @@ public ParallelExecutionSettings(
/// <summary>
/// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct.
/// </summary>
/// <param name="maxDegreeOfParallelism">The value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.</param>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/>.</param>
public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator)
: this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator)
{
}

/// <summary>
/// Gets the MemoryAllocator
/// Gets the <see cref="MemoryAllocator"/>.
/// </summary>
public MemoryAllocator MemoryAllocator { get; }

Expand All @@ -60,12 +76,26 @@ public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator mem
/// Creates a new instance of <see cref="ParallelExecutionSettings"/>
/// having <see cref="MinimumPixelsProcessedPerTask"/> multiplied by <paramref name="multiplier"/>
/// </summary>
/// <param name="multiplier">The value to multiply <see cref="MinimumPixelsProcessedPerTask"/> with.</param>
/// <returns>The modified <see cref="ParallelExecutionSettings"/>.</returns>
public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier)
{
Guard.MustBeGreaterThan(multiplier, 0, nameof(multiplier));

return new ParallelExecutionSettings(
this.MaxDegreeOfParallelism,
this.MinimumPixelsProcessedPerTask * multiplier,
this.MemoryAllocator);
}

/// <summary>
/// Get the default <see cref="SixLabors.ImageSharp.Advanced.ParallelUtils.ParallelExecutionSettings"/> for a <see cref="SixLabors.ImageSharp.Configuration"/>
/// </summary>
/// <param name="configuration">The <see cref="Configuration"/>.</param>
/// <returns>The <see cref="ParallelExecutionSettings"/>.</returns>
public static ParallelExecutionSettings FromConfiguration(Configuration configuration)
{
return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,45 @@
using SixLabors.Memory;
using SixLabors.Primitives;

namespace SixLabors.ImageSharp.ParallelUtils
namespace SixLabors.ImageSharp.Advanced.ParallelUtils
{
/// <summary>
/// Utility methods for batched processing of pixel row intervals.
/// Parallel execution is optimized for image processing.
/// Use this instead of direct <see cref="Parallel"/> calls!
/// Parallel execution is optimized for image processing based on values defined
/// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>.
/// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods.
/// </summary>
internal static class ParallelHelper
public static class ParallelHelper
{
/// <summary>
/// Get the default <see cref="ParallelExecutionSettings"/> for a <see cref="Configuration"/>
/// </summary>
public static ParallelExecutionSettings GetParallelSettings(this Configuration configuration)
{
return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator);
}

/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows(Rectangle rectangle, Configuration configuration, Action<RowInterval> body)
{
ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings();
ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);

IterateRows(rectangle, parallelSettings, body);
}

/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
Action<RowInterval> body)
{
ValidateRectangle(rectangle);

int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask);
int maxSteps = DivideCeil(
rectangle.Width * rectangle.Height,
parallelSettings.MinimumPixelsProcessedPerTask);

int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);

Expand Down Expand Up @@ -81,7 +82,7 @@ public static void IterateRows(
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="body"/> invocation.
/// </summary>
public static void IterateRowsWithTempBuffer<T>(
internal static void IterateRowsWithTempBuffer<T>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this is internal because Drawing does not use it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I'm not fully happy with this API yet, so I'd keep it internal for now.

Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
Action<RowInterval, Memory<T>> body)
Expand Down Expand Up @@ -133,13 +134,13 @@ public static void IterateRowsWithTempBuffer<T>(
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="body"/> invocation.
/// </summary>
public static void IterateRowsWithTempBuffer<T>(
internal static void IterateRowsWithTempBuffer<T>(
Rectangle rectangle,
Configuration configuration,
Action<RowInterval, Memory<T>> body)
where T : unmanaged
{
IterateRowsWithTempBuffer(rectangle, configuration.GetParallelSettings(), body);
IterateRowsWithTempBuffer(rectangle, ParallelExecutionSettings.FromConfiguration(configuration), body);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
4 changes: 2 additions & 2 deletions src/ImageSharp/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public int MaxDegreeOfParallelism
get => this.maxDegreeOfParallelism;
set
{
if (value <= 0)
if (value == 0 || value < -1)
{
throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism));
}
Expand Down Expand Up @@ -161,4 +161,4 @@ internal static Configuration CreateDefaultInstance()
new BmpConfigurationModule());
}
}
}
}
2 changes: 1 addition & 1 deletion src/ImageSharp/ImageFrameCollection{TPixel}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ private ImageFrame<TPixel> CopyNonCompatibleFrame(ImageFrame source)
this.parent.GetConfiguration(),
source.Size(),
source.Metadata.DeepClone());
source.CopyPixelsTo(result.PixelBuffer.Span);
source.CopyPixelsTo(result.PixelBuffer.GetSpan());
return result;
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/ImageSharp/ImageFrame{TPixel}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
using System.Runtime.InteropServices;

using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
Expand Down Expand Up @@ -218,10 +218,10 @@ internal override void CopyPixelsTo<TDestinationPixel>(Span<TDestinationPixel> d
if (typeof(TPixel) == typeof(TDestinationPixel))
{
Span<TPixel> dest1 = MemoryMarshal.Cast<TDestinationPixel, TPixel>(destination);
this.PixelBuffer.Span.CopyTo(dest1);
this.PixelBuffer.GetSpan().CopyTo(dest1);
}

PixelOperations<TPixel>.Instance.To(this.Configuration, this.PixelBuffer.Span, destination);
PixelOperations<TPixel>.Instance.To(this.Configuration, this.PixelBuffer.GetSpan(), destination);
}

/// <inheritdoc/>
Expand Down
Loading