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.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using System; |
using System; |
||||
using System.Buffers; |
using System.Buffers; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using SixLabors.ImageSharp.Advanced; |
using SixLabors.ImageSharp.Advanced; |
||||
using SixLabors.ImageSharp.Memory; |
using SixLabors.ImageSharp.Memory; |
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.ParallelUtils; |
||||
using SixLabors.Memory; |
using SixLabors.ImageSharp.PixelFormats; |
||||
using SixLabors.Primitives; |
using SixLabors.Memory; |
||||
|
using SixLabors.Primitives; |
||||
namespace SixLabors.ImageSharp.Processing.Processors.Drawing |
|
||||
{ |
namespace SixLabors.ImageSharp.Processing.Processors.Drawing |
||||
/// <summary>
|
{ |
||||
/// Combines two images together by blending the pixels.
|
/// <summary>
|
||||
/// </summary>
|
/// Combines two images together by blending the pixels.
|
||||
|
/// </summary>
|
||||
/// <typeparam name="TPixelDst">The pixel format of destination image.</typeparam>
|
/// <typeparam name="TPixelDst">The pixel format of destination image.</typeparam>
|
||||
/// <typeparam name="TPixelSrc">The pixel format os source image.</typeparam>
|
/// <typeparam name="TPixelSrc">The pixel format of source image.</typeparam>
|
||||
internal class DrawImageProcessor<TPixelDst, TPixelSrc> : ImageProcessor<TPixelDst> |
internal class DrawImageProcessor<TPixelDst, TPixelSrc> : ImageProcessor<TPixelDst> |
||||
where TPixelDst : struct, IPixel<TPixelDst> |
where TPixelDst : struct, IPixel<TPixelDst> |
||||
where TPixelSrc : struct, IPixel<TPixelSrc> |
where TPixelSrc : struct, IPixel<TPixelSrc> |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelDst, TPixelSrc}"/> class.
|
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelDst, TPixelSrc}"/> class.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="image">The image to blend with the currently processing image.</param>
|
/// <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="location">The location to draw the blended image.</param>
|
||||
/// <param name="colorBlendingMode">The blending mode to use when drawing the 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="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>
|
/// <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) |
public DrawImageProcessor(Image<TPixelSrc> image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity) |
||||
{ |
{ |
||||
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); |
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); |
||||
|
|
||||
this.Image = image; |
this.Image = image; |
||||
this.Opacity = opacity; |
this.Opacity = opacity; |
||||
this.Blender = PixelOperations<TPixelDst>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); |
this.Blender = PixelOperations<TPixelDst>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); |
||||
this.Location = location; |
this.Location = location; |
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets the image to blend
|
/// Gets the image to blend
|
||||
/// </summary>
|
/// </summary>
|
||||
public Image<TPixelSrc> Image { get; } |
public Image<TPixelSrc> Image { get; } |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets the opacity of the image to blend
|
/// Gets the opacity of the image to blend
|
||||
/// </summary>
|
/// </summary>
|
||||
public float Opacity { get; } |
public float Opacity { get; } |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets the pixel blender
|
/// Gets the pixel blender
|
||||
/// </summary>
|
/// </summary>
|
||||
public PixelBlender<TPixelDst> Blender { get; } |
public PixelBlender<TPixelDst> Blender { get; } |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets the location to draw the blended image
|
/// Gets the location to draw the blended image
|
||||
/// </summary>
|
/// </summary>
|
||||
public Point Location { get; } |
public Point Location { get; } |
||||
|
|
||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||
protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sourceRectangle, Configuration configuration) |
protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sourceRectangle, Configuration configuration) |
||||
{ |
{ |
||||
Image<TPixelSrc> targetImage = this.Image; |
Image<TPixelSrc> targetImage = this.Image; |
||||
PixelBlender<TPixelDst> blender = this.Blender; |
PixelBlender<TPixelDst> blender = this.Blender; |
||||
int locationY = this.Location.Y; |
int locationY = this.Location.Y; |
||||
|
|
||||
// Align start/end positions.
|
// Align start/end positions.
|
||||
Rectangle bounds = targetImage.Bounds(); |
Rectangle bounds = targetImage.Bounds(); |
||||
|
|
||||
int minX = Math.Max(this.Location.X, sourceRectangle.X); |
int minX = Math.Max(this.Location.X, sourceRectangle.X); |
||||
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); |
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); |
||||
int targetX = minX - this.Location.X; |
int targetX = minX - this.Location.X; |
||||
|
|
||||
int minY = Math.Max(this.Location.Y, sourceRectangle.Y); |
int minY = Math.Max(this.Location.Y, sourceRectangle.Y); |
||||
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); |
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); |
||||
|
|
||||
int width = maxX - minX; |
int width = maxX - minX; |
||||
|
|
||||
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator; |
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator; |
||||
|
|
||||
ParallelFor.WithConfiguration( |
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); |
||||
minY, |
|
||||
maxY, |
ParallelHelper.IterateRows( |
||||
configuration, |
workingRect, |
||||
y => |
configuration, |
||||
{ |
rows => |
||||
Span<TPixelDst> background = source.GetPixelRowSpan(y).Slice(minX, width); |
{ |
||||
Span<TPixelSrc> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); |
for (int y = rows.Min; y < rows.Max; y++) |
||||
blender.Blend<TPixelSrc>(memoryAllocator, background, background, foreground, this.Opacity); |
{ |
||||
}); |
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.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
using SixLabors.ImageSharp.Processing; |
using SixLabors.ImageSharp.Processing; |
||||
|
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
using Xunit; |
using Xunit; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms |
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms |
||||
{ |
{ |
||||
|
[GroupOutput("Transforms")] |
||||
public class CropTest : FileTestBase |
public class CropTest : FileTestBase |
||||
{ |
{ |
||||
[Theory] |
[Theory] |
||||
[WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] |
[WithTestPatternImages(70, 30, PixelTypes.Rgba32, 0, 0, 70, 30)] |
||||
public void ImageShouldCrop<TPixel>(TestImageProvider<TPixel> provider) |
[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> |
where TPixel : struct, IPixel<TPixel> |
||||
{ |
{ |
||||
using (Image<TPixel> image = provider.GetImage()) |
var rect = new Rectangle(x, y, w, h); |
||||
{ |
FormattableString info = $"X{x}Y{y}.W{w}H{h}"; |
||||
image.Mutate(x => x.Crop(image.Width / 2, image.Height / 2)); |
provider.RunValidatingProcessorTest( |
||||
image.DebugSave(provider); |
ctx => ctx.Crop(rect), |
||||
} |
info, |
||||
|
appendPixelTypeToFileName: false, |
||||
|
comparer: ImageComparer.Exact); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -1 +1 @@ |
|||||
Subproject commit 6abc3bc0ac253a24c9e88e68d7b7d853350a85da |
Subproject commit 7f4d2d64c6b820ca2b6827e6a8540a1013305ccf |
||||
Loading…
Reference in new issue