mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
39 changed files with 1898 additions and 1206 deletions
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Combines two images together by blending the pixels.
|
|||
/// </summary>
|
|||
// 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 |
|||
{ |
|||
/// <summary>
|
|||
/// Combines two images together by blending the pixels.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixelDst">The pixel format of destination image.</typeparam>
|
|||
/// <typeparam name="TPixelSrc">The pixel format os source image.</typeparam>
|
|||
internal class DrawImageProcessor<TPixelDst, TPixelSrc> : ImageProcessor<TPixelDst> |
|||
/// <typeparam name="TPixelSrc">The pixel format of source image.</typeparam>
|
|||
internal class DrawImageProcessor<TPixelDst, TPixelSrc> : ImageProcessor<TPixelDst> |
|||
where TPixelDst : struct, IPixel<TPixelDst> |
|||
where TPixelSrc : struct, IPixel<TPixelSrc> |
|||
where TPixelSrc : struct, IPixel<TPixelSrc> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelDst, TPixelSrc}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="image">The image to blend with the currently processing image.</param>
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelDst, TPixelSrc}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="image">The image to blend with the currently processing image.</param>
|
|||
/// <param name="location">The location to draw the blended image.</param>
|
|||
/// <param name="colorBlendingMode">The blending mode to use when drawing the image.</param>
|
|||
/// <param name="alphaCompositionMode">The Alpha blending mode to use when drawing the image.</param>
|
|||
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
|
|||
public DrawImageProcessor(Image<TPixelSrc> 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<TPixelDst>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); |
|||
this.Location = location; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the image to blend
|
|||
/// </summary>
|
|||
public Image<TPixelSrc> Image { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the opacity of the image to blend
|
|||
/// </summary>
|
|||
public float Opacity { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the pixel blender
|
|||
/// </summary>
|
|||
public PixelBlender<TPixelDst> Blender { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the location to draw the blended image
|
|||
/// </summary>
|
|||
public Point Location { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sourceRectangle, Configuration configuration) |
|||
{ |
|||
Image<TPixelSrc> targetImage = this.Image; |
|||
PixelBlender<TPixelDst> 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; |
|||
|
|||
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
|
|||
public DrawImageProcessor(Image<TPixelSrc> 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<TPixelDst>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); |
|||
this.Location = location; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the image to blend
|
|||
/// </summary>
|
|||
public Image<TPixelSrc> Image { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the opacity of the image to blend
|
|||
/// </summary>
|
|||
public float Opacity { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the pixel blender
|
|||
/// </summary>
|
|||
public PixelBlender<TPixelDst> Blender { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the location to draw the blended image
|
|||
/// </summary>
|
|||
public Point Location { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sourceRectangle, Configuration configuration) |
|||
{ |
|||
Image<TPixelSrc> targetImage = this.Image; |
|||
PixelBlender<TPixelDst> 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<TPixelDst> background = source.GetPixelRowSpan(y).Slice(minX, width); |
|||
Span<TPixelSrc> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); |
|||
blender.Blend<TPixelSrc>(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<TPixelDst> background = source.GetPixelRowSpan(y).Slice(minX, width); |
|||
Span<TPixelSrc> foreground = |
|||
targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); |
|||
blender.Blend<TPixelSrc>(memoryAllocator, background, background, foreground, this.Opacity); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,61 +0,0 @@ |
|||
using System; |
|||
using System.Buffers; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp |
|||
{ |
|||
/// <summary>
|
|||
/// Utility methods for Parallel.For() execution. Use this instead of raw <see cref="Parallel"/> calls!
|
|||
/// </summary>
|
|||
internal static class ParallelFor |
|||
{ |
|||
/// <summary>
|
|||
/// Helper method to execute Parallel.For using the settings in <paramref name="configuration"/>
|
|||
/// </summary>
|
|||
public static void WithConfiguration(int fromInclusive, int toExclusive, Configuration configuration, Action<int> body) |
|||
{ |
|||
Parallel.For(fromInclusive, toExclusive, configuration.GetParallelOptions(), body); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Helper method to execute Parallel.For with temporary worker buffer shared between executing tasks.
|
|||
/// The buffer is not guaranteed to be clean!
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The value type of the buffer</typeparam>
|
|||
/// <param name="fromInclusive">The start index, inclusive.</param>
|
|||
/// <param name="toExclusive">The end index, exclusive.</param>
|
|||
/// <param name="configuration">The <see cref="Configuration"/> used for getting the <see cref="MemoryAllocator"/> and <see cref="ParallelOptions"/></param>
|
|||
/// <param name="bufferLength">The length of the requested parallel buffer</param>
|
|||
/// <param name="body">The delegate that is invoked once per iteration.</param>
|
|||
public static void WithTemporaryBuffer<T>( |
|||
int fromInclusive, |
|||
int toExclusive, |
|||
Configuration configuration, |
|||
int bufferLength, |
|||
Action<int, IMemoryOwner<T>> body) |
|||
where T : struct |
|||
{ |
|||
MemoryAllocator memoryAllocator = configuration.MemoryAllocator; |
|||
ParallelOptions parallelOptions = configuration.GetParallelOptions(); |
|||
|
|||
IMemoryOwner<T> InitBuffer() |
|||
{ |
|||
return memoryAllocator.Allocate<T>(bufferLength); |
|||
} |
|||
|
|||
void CleanUpBuffer(IMemoryOwner<T> buffer) |
|||
{ |
|||
buffer.Dispose(); |
|||
} |
|||
|
|||
IMemoryOwner<T> BodyFunc(int i, ParallelLoopState state, IMemoryOwner<T> buffer) |
|||
{ |
|||
body(i, buffer); |
|||
return buffer; |
|||
} |
|||
|
|||
Parallel.For(fromInclusive, toExclusive, parallelOptions, InitBuffer, BodyFunc, CleanUpBuffer); |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Defines execution settings for methods in <see cref="ParallelHelper"/>.
|
|||
/// </summary>
|
|||
internal readonly struct ParallelExecutionSettings |
|||
{ |
|||
/// <summary>
|
|||
/// Default value for <see cref="MinimumPixelsProcessedPerTask"/>.
|
|||
/// </summary>
|
|||
public const int DefaultMinimumPixelsProcessedPerTask = 4096; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct.
|
|||
/// </summary>
|
|||
public ParallelExecutionSettings( |
|||
int maxDegreeOfParallelism, |
|||
int minimumPixelsProcessedPerTask, |
|||
MemoryAllocator memoryAllocator) |
|||
{ |
|||
this.MaxDegreeOfParallelism = maxDegreeOfParallelism; |
|||
this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask; |
|||
this.MemoryAllocator = memoryAllocator; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct.
|
|||
/// </summary>
|
|||
public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) |
|||
: this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the MemoryAllocator
|
|||
/// </summary>
|
|||
public MemoryAllocator MemoryAllocator { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.
|
|||
/// </summary>
|
|||
public int MaxDegreeOfParallelism { get; } |
|||
|
|||
/// <summary>
|
|||
/// 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 <see cref="DefaultMinimumPixelsProcessedPerTask"/> by default,
|
|||
/// the optimum value is operation specific. (The cheaper the operation, the larger the value is.)
|
|||
/// </summary>
|
|||
public int MinimumPixelsProcessedPerTask { get; } |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of <see cref="ParallelExecutionSettings"/>
|
|||
/// having <see cref="MinimumPixelsProcessedPerTask"/> multiplied by <paramref name="multiplier"/>
|
|||
/// </summary>
|
|||
public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier) |
|||
{ |
|||
return new ParallelExecutionSettings( |
|||
this.MaxDegreeOfParallelism, |
|||
this.MinimumPixelsProcessedPerTask * multiplier, |
|||
this.MemoryAllocator); |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <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!
|
|||
/// </summary>
|
|||
internal 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>
|
|||
public static void IterateRows(Rectangle rectangle, Configuration configuration, Action<RowInterval> body) |
|||
{ |
|||
ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings(); |
|||
|
|||
IterateRows(rectangle, parallelSettings, body); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
|
|||
/// </summary>
|
|||
public static void IterateRows( |
|||
Rectangle rectangle, |
|||
in ParallelExecutionSettings parallelSettings, |
|||
Action<RowInterval> 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); |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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>( |
|||
Rectangle rectangle, |
|||
in ParallelExecutionSettings parallelSettings, |
|||
Action<RowInterval, Memory<T>> 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<T> buffer = memoryAllocator.Allocate<T>(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<T> buffer = memoryAllocator.Allocate<T>(rectangle.Width)) |
|||
{ |
|||
body(rows, buffer.Memory); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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>( |
|||
Rectangle rectangle, |
|||
Configuration configuration, |
|||
Action<RowInterval, Memory<T>> body) |
|||
where T : struct |
|||
{ |
|||
IterateRowsWithTempBuffer(rectangle, configuration.GetParallelSettings(), body); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> |
|||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common/@EntryIndexedValue">True</s:Boolean> |
|||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common_005Cexceptions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an interval of rows in a <see cref="Rectangle"/> and/or <see cref="Buffer2D{T}"/>
|
|||
/// </summary>
|
|||
internal readonly struct RowInterval |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RowInterval"/> struct.
|
|||
/// </summary>
|
|||
public RowInterval(int min, int max) |
|||
{ |
|||
DebugGuard.MustBeLessThan(min, max, nameof(min)); |
|||
|
|||
this.Min = min; |
|||
this.Max = max; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the INCLUSIVE minimum
|
|||
/// </summary>
|
|||
public int Min { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the EXCLUSIVE maximum
|
|||
/// </summary>
|
|||
public int Max { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the difference (<see cref="Max"/> - <see cref="Min"/>)
|
|||
/// </summary>
|
|||
public int Height => this.Max - this.Min; |
|||
|
|||
/// <inheritdoc />
|
|||
public override string ToString() |
|||
{ |
|||
return $"RowInterval [{this.Min}->{this.Max}["; |
|||
} |
|||
} |
|||
} |
|||
@ -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<Rgba32>(1024, 768)) |
|||
using (var target = new Image<Rgba32>(1024, 768)) |
|||
{ |
|||
Buffer2D<Rgba32> sourcePixels = source.GetRootFramePixelBuffer(); |
|||
Buffer2D<Rgba32> 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<Rgba32>(1024, 768)) |
|||
using (var target = new Image<Rgba32>(1024, 768)) |
|||
{ |
|||
Buffer2D<Rgba32> sourcePixels = source.GetRootFramePixelBuffer(); |
|||
Buffer2D<Rgba32> targetPixels = target.GetRootFramePixelBuffer(); |
|||
ParallelFor.WithConfiguration( |
|||
0, |
|||
source.Height, |
|||
Configuration.Default, |
|||
y => |
|||
{ |
|||
Span<Rgba32> sourceRow = sourcePixels.GetRowSpan(y); |
|||
Span<Rgba32> 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<Rgba32>(1024, 768)) |
|||
using (var target = new Image<Rgba32>(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<Rgba32>(1024, 768)) |
|||
using (var target = new Image<Rgba32>(1024, 768)) |
|||
{ |
|||
ParallelFor.WithConfiguration( |
|||
0, |
|||
source.Height, |
|||
Configuration.Default, |
|||
y => |
|||
{ |
|||
Span<Rgba32> sourceRow = source.Frames.RootFrame.GetPixelRowSpan(y); |
|||
Span<Rgba32> targetRow = target.Frames.RootFrame.GetPixelRowSpan(y); |
|||
|
|||
for (int x = 0; x < source.Width; x++) |
|||
{ |
|||
targetRow[x] = sourceRow[x]; |
|||
} |
|||
}); |
|||
|
|||
return target[0, 0]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,338 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Linq; |
|||
using System.Numerics; |
|||
using System.Threading; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.ParallelUtils; |
|||
using SixLabors.Memory; |
|||
using SixLabors.Primitives; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Helpers |
|||
{ |
|||
public class ParallelHelperTests |
|||
{ |
|||
private readonly ITestOutputHelper Output; |
|||
|
|||
public ParallelHelperTests(ITestOutputHelper output) |
|||
{ |
|||
this.Output = output; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength
|
|||
/// </summary>
|
|||
public static TheoryData<int, int, int, int, int> IterateRows_OverMinimumPixelsLimit_Data = |
|||
new TheoryData<int, int, int, int, int>() |
|||
{ |
|||
{ 1, 0, 100, -1, 100 }, |
|||
{ 2, 0, 9, 5, 4 }, |
|||
{ 4, 0, 19, 5, 4 }, |
|||
{ 2, 10, 19, 5, 4 }, |
|||
{ 4, 0, 200, 50, 50 }, |
|||
{ 4, 123, 323, 50, 50 }, |
|||
{ 4, 0, 1201, 301, 298 }, |
|||
{ 8, 10, 236, 29, 23 } |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] |
|||
public void IterateRows_OverMinimumPixelsLimit_IntervalsAreCorrect( |
|||
int maxDegreeOfParallelism, |
|||
int minY, |
|||
int maxY, |
|||
int expectedStepLength, |
|||
int expectedLastStepLength) |
|||
{ |
|||
var parallelSettings = new ParallelExecutionSettings( |
|||
maxDegreeOfParallelism, |
|||
1, |
|||
Configuration.Default.MemoryAllocator); |
|||
|
|||
var rectangle = new Rectangle(0, minY, 10, maxY - minY); |
|||
|
|||
int actualNumberOfSteps = 0; |
|||
|
|||
ParallelHelper.IterateRows( |
|||
rectangle, |
|||
parallelSettings, |
|||
rows => |
|||
{ |
|||
Assert.True(rows.Min >= minY); |
|||
Assert.True(rows.Max <= maxY); |
|||
|
|||
int step = rows.Max - rows.Min; |
|||
int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; |
|||
|
|||
Interlocked.Increment(ref actualNumberOfSteps); |
|||
Assert.Equal(expected, step); |
|||
}); |
|||
|
|||
Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] |
|||
public void IterateRows_OverMinimumPixelsLimit_ShouldVisitAllRows( |
|||
int maxDegreeOfParallelism, |
|||
int minY, |
|||
int maxY, |
|||
int expectedStepLength, |
|||
int expectedLastStepLength) |
|||
{ |
|||
var parallelSettings = new ParallelExecutionSettings( |
|||
maxDegreeOfParallelism, |
|||
1, |
|||
Configuration.Default.MemoryAllocator); |
|||
|
|||
var rectangle = new Rectangle(0, minY, 10, maxY - minY); |
|||
|
|||
|
|||
int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); |
|||
int[] actualData = new int[maxY]; |
|||
|
|||
ParallelHelper.IterateRows( |
|||
rectangle, |
|||
parallelSettings, |
|||
rows => |
|||
{ |
|||
for (int y = rows.Min; y < rows.Max; y++) |
|||
{ |
|||
actualData[y] = y; |
|||
} |
|||
}); |
|||
|
|||
Assert.Equal(expectedData, actualData); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] |
|||
public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit( |
|||
int maxDegreeOfParallelism, |
|||
int minY, |
|||
int maxY, |
|||
int expectedStepLength, |
|||
int expectedLastStepLength) |
|||
{ |
|||
var parallelSettings = new ParallelExecutionSettings( |
|||
maxDegreeOfParallelism, |
|||
1, |
|||
Configuration.Default.MemoryAllocator); |
|||
|
|||
var rectangle = new Rectangle(0, minY, 10, maxY - minY); |
|||
|
|||
var bufferHashes = new ConcurrentBag<int>(); |
|||
|
|||
int actualNumberOfSteps = 0; |
|||
ParallelHelper.IterateRowsWithTempBuffer( |
|||
rectangle, |
|||
parallelSettings, |
|||
(RowInterval rows, Memory<Vector4> buffer) => |
|||
{ |
|||
Assert.True(rows.Min >= minY); |
|||
Assert.True(rows.Max <= maxY); |
|||
|
|||
bufferHashes.Add(buffer.GetHashCode()); |
|||
|
|||
int step = rows.Max - rows.Min; |
|||
int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; |
|||
|
|||
Interlocked.Increment(ref actualNumberOfSteps); |
|||
Assert.Equal(expected, step); |
|||
}); |
|||
|
|||
Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); |
|||
|
|||
int numberOfDifferentBuffers = bufferHashes.Distinct().Count(); |
|||
Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] |
|||
public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit_ShouldVisitAllRows( |
|||
int maxDegreeOfParallelism, |
|||
int minY, |
|||
int maxY, |
|||
int expectedStepLength, |
|||
int expectedLastStepLength) |
|||
{ |
|||
var parallelSettings = new ParallelExecutionSettings( |
|||
maxDegreeOfParallelism, |
|||
1, |
|||
Configuration.Default.MemoryAllocator); |
|||
|
|||
var rectangle = new Rectangle(0, minY, 10, maxY - minY); |
|||
|
|||
int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); |
|||
int[] actualData = new int[maxY]; |
|||
|
|||
ParallelHelper.IterateRowsWithTempBuffer( |
|||
rectangle, |
|||
parallelSettings, |
|||
(RowInterval rows, Memory<Vector4> buffer) => |
|||
{ |
|||
for (int y = rows.Min; y < rows.Max; y++) |
|||
{ |
|||
actualData[y] = y; |
|||
} |
|||
}); |
|||
|
|||
Assert.Equal(expectedData, actualData); |
|||
|
|||
} |
|||
|
|||
public static TheoryData<int, int, int, int, int, int, int> IterateRows_WithEffectiveMinimumPixelsLimit_Data = |
|||
new TheoryData<int, int, int, int, int, int, int>() |
|||
{ |
|||
{ 2, 200, 50, 2, 1, -1, 2 }, |
|||
{ 2, 200, 200, 1, 1, -1, 1 }, |
|||
{ 4, 200, 100, 4, 2, 2, 2 }, |
|||
{ 4, 300, 100, 8, 3, 3, 2 }, |
|||
{ 2, 5000, 1, 4500, 1, -1, 4500 }, |
|||
{ 2, 5000, 1, 5000, 1, -1, 5000 }, |
|||
{ 2, 5000, 1, 5001, 2, 2501, 2500 }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] |
|||
public void IterateRows_WithEffectiveMinimumPixelsLimit( |
|||
int maxDegreeOfParallelism, |
|||
int minimumPixelsProcessedPerTask, |
|||
int width, |
|||
int height, |
|||
int expectedNumberOfSteps, |
|||
int expectedStepLength, |
|||
int expectedLastStepLength) |
|||
{ |
|||
var parallelSettings = new ParallelExecutionSettings( |
|||
maxDegreeOfParallelism, |
|||
minimumPixelsProcessedPerTask, |
|||
Configuration.Default.MemoryAllocator); |
|||
|
|||
var rectangle = new Rectangle(0, 0, width, height); |
|||
|
|||
int actualNumberOfSteps = 0; |
|||
|
|||
ParallelHelper.IterateRows( |
|||
rectangle, |
|||
parallelSettings, |
|||
rows => |
|||
{ |
|||
Assert.True(rows.Min >= 0); |
|||
Assert.True(rows.Max <= height); |
|||
|
|||
int step = rows.Max - rows.Min; |
|||
int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; |
|||
|
|||
Interlocked.Increment(ref actualNumberOfSteps); |
|||
Assert.Equal(expected, step); |
|||
}); |
|||
|
|||
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] |
|||
public void IterateRowsWithTempBuffer_WithEffectiveMinimumPixelsLimit( |
|||
int maxDegreeOfParallelism, |
|||
int minimumPixelsProcessedPerTask, |
|||
int width, |
|||
int height, |
|||
int expectedNumberOfSteps, |
|||
int expectedStepLength, |
|||
int expectedLastStepLength) |
|||
{ |
|||
var parallelSettings = new ParallelExecutionSettings( |
|||
maxDegreeOfParallelism, |
|||
minimumPixelsProcessedPerTask, |
|||
Configuration.Default.MemoryAllocator); |
|||
|
|||
var rectangle = new Rectangle(0, 0, width, height); |
|||
|
|||
int actualNumberOfSteps = 0; |
|||
ParallelHelper.IterateRowsWithTempBuffer( |
|||
rectangle, |
|||
parallelSettings, |
|||
(RowInterval rows, Memory<Vector4> buffer) => |
|||
{ |
|||
Assert.True(rows.Min >= 0); |
|||
Assert.True(rows.Max <= height); |
|||
|
|||
int step = rows.Max - rows.Min; |
|||
int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; |
|||
|
|||
Interlocked.Increment(ref actualNumberOfSteps); |
|||
Assert.Equal(expected, step); |
|||
}); |
|||
|
|||
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); |
|||
} |
|||
|
|||
public static readonly TheoryData<int, int, int, int, int, int, int> IterateRectangularBuffer_Data = |
|||
new TheoryData<int, int, int, int, int, int, int>() |
|||
{ |
|||
{ 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox
|
|||
{ 2, 582, 453, 10, 10, 291, 226 }, |
|||
{ 16, 582, 453, 10, 10, 291, 226 }, |
|||
{ 16, 582, 453, 10, 10, 1, 226 }, |
|||
{ 16, 1, 453, 0, 10, 1, 226 }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IterateRectangularBuffer_Data))] |
|||
public void IterateRectangularBuffer( |
|||
int maxDegreeOfParallelism, |
|||
int bufferWidth, |
|||
int bufferHeight, |
|||
int rectX, |
|||
int rectY, |
|||
int rectWidth, |
|||
int rectHeight) |
|||
{ |
|||
MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator; |
|||
|
|||
using (Buffer2D<Point> expected = memoryAllocator.Allocate2D<Point>(bufferWidth, bufferHeight, AllocationOptions.Clean)) |
|||
using (Buffer2D<Point> actual = memoryAllocator.Allocate2D<Point>(bufferWidth, bufferHeight, AllocationOptions.Clean)) |
|||
{ |
|||
var rect = new Rectangle(rectX, rectY, rectWidth, rectHeight); |
|||
|
|||
void FillRow(int y, Buffer2D<Point> buffer) |
|||
{ |
|||
for (int x = rect.Left; x < rect.Right; x++) |
|||
{ |
|||
buffer[x, y] = new Point(x, y); |
|||
} |
|||
} |
|||
|
|||
// Fill Expected data:
|
|||
for (int y = rectY; y < rect.Bottom; y++) |
|||
{ |
|||
FillRow(y, expected); |
|||
} |
|||
|
|||
// Fill actual data using IterateRows:
|
|||
var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); |
|||
|
|||
ParallelHelper.IterateRows(rect, settings, |
|||
rows => |
|||
{ |
|||
this.Output.WriteLine(rows.ToString()); |
|||
for (int y = rows.Min; y < rows.Max; y++) |
|||
{ |
|||
FillRow(y, actual); |
|||
} |
|||
}); |
|||
|
|||
// Assert:
|
|||
TestImageExtensions.CompareBuffers(expected.Span, actual.Span); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.Memory; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Helpers |
|||
{ |
|||
public class RowIntervalTests |
|||
{ |
|||
[Theory] |
|||
[InlineData(10, 20, 5, 10)] |
|||
[InlineData(1, 10, 0, 10)] |
|||
[InlineData(1, 10, 5, 8)] |
|||
[InlineData(1, 1, 0, 1)] |
|||
[InlineData(10, 20, 9, 10)] |
|||
[InlineData(10, 20, 0, 1)] |
|||
public void GetMultiRowSpan(int width, int height, int min, int max) |
|||
{ |
|||
using (Buffer2D<int> buffer = Configuration.Default.MemoryAllocator.Allocate2D<int>(width, height)) |
|||
{ |
|||
var rows = new RowInterval(min, max); |
|||
|
|||
Span<int> span = buffer.GetMultiRowSpan(rows); |
|||
|
|||
ref int expected0 = ref buffer.Span[min * width]; |
|||
int expectedLength = (max - min) * width; |
|||
|
|||
ref int actual0 = ref span[0]; |
|||
|
|||
Assert.Equal(span.Length, expectedLength); |
|||
Assert.True(Unsafe.AreSame(ref expected0, ref actual0)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,24 +1,34 @@ |
|||
// 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.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.Primitives; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms |
|||
{ |
|||
[GroupOutput("Transforms")] |
|||
public class CropTest : FileTestBase |
|||
{ |
|||
[Theory] |
|||
[WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] |
|||
public void ImageShouldCrop<TPixel>(TestImageProvider<TPixel> provider) |
|||
[WithTestPatternImages(70, 30, PixelTypes.Rgba32, 0, 0, 70, 30)] |
|||
[WithTestPatternImages(50, 50, PixelTypes.Rgba32, -1, -1, 100, 200)] |
|||
[WithTestPatternImages(30, 70, PixelTypes.Rgba32, 7, 13, 20, 50)] |
|||
public void Crop<TPixel>(TestImageProvider<TPixel> provider, int x, int y, int w, int h) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
image.Mutate(x => x.Crop(image.Width / 2, image.Height / 2)); |
|||
image.DebugSave(provider); |
|||
} |
|||
var rect = new Rectangle(x, y, w, h); |
|||
FormattableString info = $"X{x}Y{y}.W{w}H{h}"; |
|||
provider.RunValidatingProcessorTest( |
|||
ctx => ctx.Crop(rect), |
|||
info, |
|||
appendPixelTypeToFileName: false, |
|||
comparer: ImageComparer.Exact); |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 6abc3bc0ac253a24c9e88e68d7b7d853350a85da |
|||
Subproject commit 7f4d2d64c6b820ca2b6827e6a8540a1013305ccf |
|||
Loading…
Reference in new issue