Browse Source

Merge pull request #1108 from SixLabors/sp/single-row-parallel-value-delegate

WIP: single row value delegates for parallel iterator
af/octree-no-pixelmap
James Jackson-South 6 years ago
committed by GitHub
parent
commit
dac2ff1db1
  1. 70
      src/ImageSharp/Advanced/IRowAction.cs
  2. 88
      src/ImageSharp/Advanced/IRowAction{TBuffer}.cs
  3. 37
      src/ImageSharp/Advanced/ParallelRowIterator.cs
  4. 17
      src/ImageSharp/ImageFrame{TPixel}.cs
  5. 29
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
  6. 111
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
  7. 84
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
  8. 84
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  9. 80
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
  10. 37
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  11. 18
      src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
  12. 95
      src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
  13. 27
      src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs
  14. 25
      src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs
  15. 46
      src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs
  16. 31
      src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs
  17. 36
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs
  18. 38
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs
  19. 81
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
  20. 29
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
  21. 17
      src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs
  22. 96
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
  23. 25
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  24. 69
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs

70
src/ImageSharp/Advanced/IRowAction.cs

@ -0,0 +1,70 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Advanced
{
/// <summary>
/// Defines the contract for an action that operates on a row.
/// </summary>
public interface IRowAction
{
/// <summary>
/// Invokes the method passing the row y coordinate.
/// </summary>
/// <param name="y">The row y coordinate.</param>
void Invoke(int y);
}
/// <summary>
/// A <see langword="struct"/> that wraps a value delegate of a specified type, and info on the memory areas to process
/// </summary>
/// <typeparam name="T">The type of value delegate to invoke</typeparam>
internal readonly struct WrappingRowAction<T>
where T : struct, IRowAction
{
public readonly int MinY;
public readonly int MaxY;
public readonly int StepY;
public readonly int MaxX;
private readonly T action;
[MethodImpl(InliningOptions.ShortMethod)]
public WrappingRowAction(int minY, int maxY, int stepY, in T action)
: this(minY, maxY, stepY, 0, action)
{
}
[MethodImpl(InliningOptions.ShortMethod)]
public WrappingRowAction(int minY, int maxY, int stepY, int maxX, in T action)
{
this.MinY = minY;
this.MaxY = maxY;
this.StepY = stepY;
this.MaxX = maxX;
this.action = action;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int i)
{
int yMin = this.MinY + (i * this.StepY);
if (yMin >= this.MaxY)
{
return;
}
int yMax = Math.Min(yMin + this.StepY, this.MaxY);
for (int y = yMin; y < yMax; y++)
{
// Skip the safety copy when invoking a potentially impure method on a readonly field
Unsafe.AsRef(this.action).Invoke(y);
}
}
}
}

88
src/ImageSharp/Advanced/IRowAction{TBuffer}.cs

@ -0,0 +1,88 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Advanced
{
/// <summary>
/// Defines the contract for an action that operates on a row with a temporary buffer.
/// </summary>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
public interface IRowAction<TBuffer>
where TBuffer : unmanaged
{
/// <summary>
/// Invokes the method passing the row and a buffer.
/// </summary>
/// <param name="y">The row y coordinate.</param>
/// <param name="span">The contiguous region of memory.</param>
void Invoke(int y, Span<TBuffer> span);
}
internal readonly struct WrappingRowAction<T, TBuffer>
where T : struct, IRowAction<TBuffer>
where TBuffer : unmanaged
{
public readonly int MinY;
public readonly int MaxY;
public readonly int StepY;
public readonly int MaxX;
private readonly MemoryAllocator allocator;
private readonly T action;
[MethodImpl(InliningOptions.ShortMethod)]
public WrappingRowAction(
int minY,
int maxY,
int stepY,
MemoryAllocator allocator,
in T action)
: this(minY, maxY, stepY, 0, allocator, action)
{
}
[MethodImpl(InliningOptions.ShortMethod)]
public WrappingRowAction(
int minY,
int maxY,
int stepY,
int maxX,
MemoryAllocator allocator,
in T action)
{
this.MinY = minY;
this.MaxY = maxY;
this.StepY = stepY;
this.MaxX = maxX;
this.allocator = allocator;
this.action = action;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int i)
{
int yMin = this.MinY + (i * this.StepY);
if (yMin >= this.MaxY)
{
return;
}
int yMax = Math.Min(yMin + this.StepY, this.MaxY);
using IMemoryOwner<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.MaxX);
Span<TBuffer> span = buffer.Memory.Span;
for (int y = yMin; y < yMax; y++)
{
Unsafe.AsRef(this.action).Invoke(y, span);
}
}
}
}

37
src/ImageSharp/Advanced/ParallelRowIterator.cs

@ -19,32 +19,32 @@ namespace SixLabors.ImageSharp.Advanced
public static class ParallelRowIterator
{
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// Iterate through the rows of a rectangle in optimized batches.
/// </summary>
/// <typeparam name="T">The type of row action to perform.</typeparam>
/// <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>
/// <param name="body">The method body defining the iteration logic on a single row.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void IterateRows<T>(Rectangle rectangle, Configuration configuration, in T body)
where T : struct, IRowIntervalAction
where T : struct, IRowAction
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, in parallelSettings, in body);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// Iterate through the rows of a rectangle in optimized batches.
/// </summary>
/// <typeparam name="T">The type of row action to perform.</typeparam>
/// <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>
/// <param name="body">The method body defining the iteration logic on a single row.</param>
public static void IterateRows<T>(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
in T body)
where T : struct, IRowIntervalAction
where T : struct, IRowAction
{
ValidateRectangle(rectangle);
@ -59,15 +59,17 @@ namespace SixLabors.ImageSharp.Advanced
// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
{
var rows = new RowInterval(top, bottom);
Unsafe.AsRef(body).Invoke(in rows);
for (int y = top; y < bottom; y++)
{
Unsafe.AsRef(body).Invoke(y);
}
return;
}
int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep);
var rowAction = new WrappingRowIntervalAction<T>(in rowInfo, in body);
var rowAction = new WrappingRowAction<T>(top, bottom, verticalStep, in body);
Parallel.For(
0,
@ -86,7 +88,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <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<T, TBuffer>(Rectangle rectangle, Configuration configuration, in T body)
where T : struct, IRowIntervalAction<TBuffer>
where T : struct, IRowAction<TBuffer>
where TBuffer : unmanaged
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
@ -101,7 +103,7 @@ namespace SixLabors.ImageSharp.Advanced
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
in T body)
where T : struct, IRowIntervalAction<TBuffer>
where T : struct, IRowAction<TBuffer>
where TBuffer : unmanaged
{
ValidateRectangle(rectangle);
@ -118,10 +120,14 @@ namespace SixLabors.ImageSharp.Advanced
// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
{
var rows = new RowInterval(top, bottom);
using (IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(width))
{
Unsafe.AsRef(body).Invoke(rows, buffer.Memory);
Span<TBuffer> span = buffer.Memory.Span;
for (int y = top; y < bottom; y++)
{
Unsafe.AsRef(body).Invoke(y, span);
}
}
return;
@ -129,8 +135,7 @@ namespace SixLabors.ImageSharp.Advanced
int verticalStep = DivideCeil(height, numOfSteps);
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width);
var rowAction = new WrappingRowIntervalBufferAction<T, TBuffer>(in rowInfo, allocator, in body);
var rowAction = new WrappingRowAction<T, TBuffer>(top, bottom, verticalStep, width, allocator, in body);
Parallel.For(
0,

17
src/ImageSharp/ImageFrame{TPixel}.cs

@ -263,7 +263,7 @@ namespace SixLabors.ImageSharp
ParallelRowIterator.IterateRows(
this.Bounds(),
configuration,
new RowIntervalAction<TPixel2>(this, target, configuration));
new RowAction<TPixel2>(this, target, configuration));
return target;
}
@ -289,7 +289,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// A <see langword="struct"/> implementing the clone logic for <see cref="ImageFrame{TPixel}"/>.
/// </summary>
private readonly struct RowIntervalAction<TPixel2> : IRowIntervalAction
private readonly struct RowAction<TPixel2> : IRowAction
where TPixel2 : struct, IPixel<TPixel2>
{
private readonly ImageFrame<TPixel> source;
@ -297,7 +297,7 @@ namespace SixLabors.ImageSharp
private readonly Configuration configuration;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
ImageFrame<TPixel> source,
ImageFrame<TPixel2> target,
Configuration configuration)
@ -309,14 +309,11 @@ namespace SixLabors.ImageSharp
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
Span<TPixel2> targetRow = this.target.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.To(this.configuration, sourceRow, targetRow);
}
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
Span<TPixel2> targetRow = this.target.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.To(this.configuration, sourceRow, targetRow);
}
}
}

29
src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs

@ -4,7 +4,6 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization
@ -54,13 +53,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
ParallelRowIterator.IterateRows(
workingRect,
configuration,
new RowIntervalAction(source, upper, lower, threshold, startX, endX, isAlphaOnly));
new RowAction(source, upper, lower, threshold, startX, endX, isAlphaOnly));
}
/// <summary>
/// A <see langword="struct"/> implementing the clone logic for <see cref="BinaryThresholdProcessor{TPixel}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction
private readonly struct RowAction : IRowAction
{
private readonly ImageFrame<TPixel> source;
private readonly TPixel upper;
@ -71,7 +70,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
private readonly bool isAlphaOnly;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
ImageFrame<TPixel> source,
TPixel upper,
TPixel lower,
@ -91,22 +90,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
Rgba32 rgba = default;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> row = this.source.GetPixelRowSpan(y);
for (int x = this.startX; x < this.endX; x++)
{
ref TPixel color = ref row[x];
color.ToRgba32(ref rgba);
Span<TPixel> row = this.source.GetPixelRowSpan(y);
for (int x = this.startX; x < this.endX; x++)
{
ref TPixel color = ref row[x];
color.ToRgba32(ref rgba);
// Convert to grayscale using ITU-R Recommendation BT.709 if required
byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
color = luminance >= this.threshold ? this.upper : this.lower;
}
// Convert to grayscale using ITU-R Recommendation BT.709 if required
byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
color = luminance >= this.threshold ? this.upper : this.lower;
}
}
}

111
src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs

@ -268,10 +268,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
// Preliminary gamma highlight pass
ParallelRowIterator.IterateRows<ApplyGammaExposureRowIntervalAction, Vector4>(
ParallelRowIterator.IterateRows<ApplyGammaExposureRowAction, Vector4>(
this.SourceRectangle,
this.Configuration,
new ApplyGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma));
new ApplyGammaExposureRowAction(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma));
// Create a 0-filled buffer to use to store the result of the component convolutions
using Buffer2D<Vector4> processingBuffer = this.Configuration.MemoryAllocator.Allocate2D<Vector4>(source.Size(), AllocationOptions.Clean);
@ -285,7 +285,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
ParallelRowIterator.IterateRows(
this.SourceRectangle,
this.Configuration,
new ApplyInverseGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma));
new ApplyInverseGammaExposureRowAction(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma));
}
/// <summary>
@ -317,20 +317,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
ParallelRowIterator.IterateRows(
sourceRectangle,
configuration,
new ApplyVerticalConvolutionRowIntervalAction(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel));
new ApplyVerticalConvolutionRowAction(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel));
// Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer
ParallelRowIterator.IterateRows(
sourceRectangle,
configuration,
new ApplyHorizontalConvolutionRowIntervalAction(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W));
new ApplyHorizontalConvolutionRowAction(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W));
}
}
/// <summary>
/// A <see langword="struct"/> implementing the vertical convolution logic for <see cref="BokehBlurProcessor{T}"/>.
/// </summary>
private readonly struct ApplyVerticalConvolutionRowIntervalAction : IRowIntervalAction
private readonly struct ApplyVerticalConvolutionRowAction : IRowAction
{
private readonly Rectangle bounds;
private readonly Buffer2D<ComplexVector4> targetValues;
@ -340,7 +340,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)]
public ApplyVerticalConvolutionRowIntervalAction(
public ApplyVerticalConvolutionRowAction(
ref Rectangle bounds,
Buffer2D<ComplexVector4> targetValues,
Buffer2D<TPixel> sourcePixels,
@ -356,16 +356,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<ComplexVector4> targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X);
Span<ComplexVector4> targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X);
for (int x = 0; x < this.bounds.Width; x++)
{
Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX);
}
for (int x = 0; x < this.bounds.Width; x++)
{
Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX);
}
}
}
@ -373,7 +370,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// A <see langword="struct"/> implementing the horizontal convolution logic for <see cref="BokehBlurProcessor{T}"/>.
/// </summary>
private readonly struct ApplyHorizontalConvolutionRowIntervalAction : IRowIntervalAction
private readonly struct ApplyHorizontalConvolutionRowAction : IRowAction
{
private readonly Rectangle bounds;
private readonly Buffer2D<Vector4> targetValues;
@ -385,7 +382,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)]
public ApplyHorizontalConvolutionRowIntervalAction(
public ApplyHorizontalConvolutionRowAction(
ref Rectangle bounds,
Buffer2D<Vector4> targetValues,
Buffer2D<ComplexVector4> sourceValues,
@ -405,16 +402,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<Vector4> targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X);
Span<Vector4> targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X);
for (int x = 0; x < this.bounds.Width; x++)
{
Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w);
}
for (int x = 0; x < this.bounds.Width; x++)
{
Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w);
}
}
}
@ -422,7 +416,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// A <see langword="struct"/> implementing the gamma exposure logic for <see cref="BokehBlurProcessor{T}"/>.
/// </summary>
private readonly struct ApplyGammaExposureRowIntervalAction : IRowIntervalAction<Vector4>
private readonly struct ApplyGammaExposureRowAction : IRowAction<Vector4>
{
private readonly Rectangle bounds;
private readonly Buffer2D<TPixel> targetPixels;
@ -430,7 +424,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly float gamma;
[MethodImpl(InliningOptions.ShortMethod)]
public ApplyGammaExposureRowIntervalAction(
public ApplyGammaExposureRowAction(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Configuration configuration,
@ -444,34 +438,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Memory<Vector4> memory)
public void Invoke(int y, Span<Vector4> span)
{
Span<Vector4> vectorSpan = memory.Span;
int length = vectorSpan.Length;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply);
ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan);
int length = span.Length;
for (int x = 0; x < this.bounds.Width; x++)
{
ref Vector4 v = ref Unsafe.Add(ref baseRef, x);
v.X = MathF.Pow(v.X, this.gamma);
v.Y = MathF.Pow(v.Y, this.gamma);
v.Z = MathF.Pow(v.Z, this.gamma);
}
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span, PixelConversionModifiers.Premultiply);
ref Vector4 baseRef = ref MemoryMarshal.GetReference(span);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorSpan.Slice(0, length), targetRowSpan);
for (int x = 0; x < this.bounds.Width; x++)
{
ref Vector4 v = ref Unsafe.Add(ref baseRef, x);
v.X = MathF.Pow(v.X, this.gamma);
v.Y = MathF.Pow(v.Y, this.gamma);
v.Z = MathF.Pow(v.Z, this.gamma);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span.Slice(0, length), targetRowSpan);
}
}
/// <summary>
/// A <see langword="struct"/> implementing the inverse gamma exposure logic for <see cref="BokehBlurProcessor{T}"/>.
/// </summary>
private readonly struct ApplyInverseGammaExposureRowIntervalAction : IRowIntervalAction
private readonly struct ApplyInverseGammaExposureRowAction : IRowAction
{
private readonly Rectangle bounds;
private readonly Buffer2D<TPixel> targetPixels;
@ -480,7 +470,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly float inverseGamma;
[MethodImpl(InliningOptions.ShortMethod)]
public ApplyInverseGammaExposureRowIntervalAction(
public ApplyInverseGammaExposureRowAction(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Buffer2D<Vector4> sourceValues,
@ -496,28 +486,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
Vector4 low = Vector4.Zero;
var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
Span<Vector4> sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X);
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan);
for (int x = 0; x < this.bounds.Width; x++)
{
ref Vector4 v = ref Unsafe.Add(ref sourceRef, x);
var clamp = Vector4.Clamp(v, low, high);
v.X = MathF.Pow(clamp.X, this.inverseGamma);
v.Y = MathF.Pow(clamp.Y, this.inverseGamma);
v.Z = MathF.Pow(clamp.Z, this.inverseGamma);
}
Span<TPixel> targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
Span<Vector4> sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X);
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply);
for (int x = 0; x < this.bounds.Width; x++)
{
ref Vector4 v = ref Unsafe.Add(ref sourceRef, x);
var clamp = Vector4.Clamp(v, low, high);
v.X = MathF.Pow(clamp.X, this.inverseGamma);
v.Y = MathF.Pow(clamp.Y, this.inverseGamma);
v.Z = MathF.Pow(clamp.Z, this.inverseGamma);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply);
}
}
}

84
src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs

@ -66,10 +66,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
ParallelRowIterator.IterateRows<RowIntervalAction, Vector4>(
ParallelRowIterator.IterateRows<RowAction, Vector4>(
interest,
this.Configuration,
new RowIntervalAction(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha));
new RowAction(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha));
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="Convolution2DProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction<Vector4>
private readonly struct RowAction : IRowAction<Vector4>
{
private readonly Rectangle bounds;
private readonly int maxY;
@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly bool preserveAlpha;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
@ -112,54 +112,50 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Memory<Vector4> memory)
public void Invoke(int y, Span<Vector4> span)
{
Span<Vector4> vectorSpan = memory.Span;
int length = vectorSpan.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
int length = span.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(span);
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan);
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span);
if (this.preserveAlpha)
if (this.preserveAlpha)
{
for (int x = 0; x < this.bounds.Width; x++)
{
for (int x = 0; x < this.bounds.Width; x++)
{
DenseMatrixUtils.Convolve2D3(
in this.kernelY,
in this.kernelX,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
this.maxY,
this.bounds.X,
this.maxX);
}
DenseMatrixUtils.Convolve2D3(
in this.kernelY,
in this.kernelX,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
this.maxY,
this.bounds.X,
this.maxX);
}
else
}
else
{
for (int x = 0; x < this.bounds.Width; x++)
{
for (int x = 0; x < this.bounds.Width; x++)
{
DenseMatrixUtils.Convolve2D4(
in this.kernelY,
in this.kernelX,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
this.maxY,
this.bounds.X,
this.maxX);
}
DenseMatrixUtils.Convolve2D4(
in this.kernelY,
in this.kernelX,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
this.maxY,
this.bounds.X,
this.maxX);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
}
}
}

84
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

@ -64,22 +64,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
// Horizontal convolution
ParallelRowIterator.IterateRows<RowIntervalAction, Vector4>(
ParallelRowIterator.IterateRows<RowAction, Vector4>(
interest,
this.Configuration,
new RowIntervalAction(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha));
new RowAction(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha));
// Vertical convolution
ParallelRowIterator.IterateRows<RowIntervalAction, Vector4>(
ParallelRowIterator.IterateRows<RowAction, Vector4>(
interest,
this.Configuration,
new RowIntervalAction(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha));
new RowAction(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha));
}
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="Convolution2PassProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction<Vector4>
private readonly struct RowAction : IRowAction<Vector4>
{
private readonly Rectangle bounds;
private readonly Buffer2D<TPixel> targetPixels;
@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly bool preserveAlpha;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
@ -107,55 +107,51 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Memory<Vector4> memory)
public void Invoke(int y, Span<Vector4> span)
{
Span<Vector4> vectorSpan = memory.Span;
int length = vectorSpan.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
int length = span.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(span);
int maxY = this.bounds.Bottom - 1;
int maxX = this.bounds.Right - 1;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan);
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span);
if (this.preserveAlpha)
if (this.preserveAlpha)
{
for (int x = 0; x < this.bounds.Width; x++)
{
for (int x = 0; x < this.bounds.Width; x++)
{
DenseMatrixUtils.Convolve3(
in this.kernel,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
maxY,
this.bounds.X,
maxX);
}
DenseMatrixUtils.Convolve3(
in this.kernel,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
maxY,
this.bounds.X,
maxX);
}
else
}
else
{
for (int x = 0; x < this.bounds.Width; x++)
{
for (int x = 0; x < this.bounds.Width; x++)
{
DenseMatrixUtils.Convolve4(
in this.kernel,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
maxY,
this.bounds.X,
maxX);
}
DenseMatrixUtils.Convolve4(
in this.kernel,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
maxY,
this.bounds.X,
maxX);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
}
}
}

80
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs

@ -57,10 +57,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
ParallelRowIterator.IterateRows<RowIntervalAction, Vector4>(
ParallelRowIterator.IterateRows<RowAction, Vector4>(
interest,
this.Configuration,
new RowIntervalAction(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha));
new RowAction(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha));
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="ConvolutionProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction<Vector4>
private readonly struct RowAction : IRowAction<Vector4>
{
private readonly Rectangle bounds;
private readonly int maxY;
@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly bool preserveAlpha;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
@ -100,52 +100,48 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Memory<Vector4> memory)
public void Invoke(int y, Span<Vector4> span)
{
Span<Vector4> vectorSpan = memory.Span;
int length = vectorSpan.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
int length = span.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(span);
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan);
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span);
if (this.preserveAlpha)
if (this.preserveAlpha)
{
for (int x = 0; x < this.bounds.Width; x++)
{
for (int x = 0; x < this.bounds.Width; x++)
{
DenseMatrixUtils.Convolve3(
in this.kernel,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
this.maxY,
this.bounds.X,
this.maxX);
}
DenseMatrixUtils.Convolve3(
in this.kernel,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
this.maxY,
this.bounds.X,
this.maxX);
}
else
}
else
{
for (int x = 0; x < this.bounds.Width; x++)
{
for (int x = 0; x < this.bounds.Width; x++)
{
DenseMatrixUtils.Convolve4(
in this.kernel,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
this.maxY,
this.bounds.X,
this.maxX);
}
DenseMatrixUtils.Convolve4(
in this.kernel,
this.sourcePixels,
ref vectorSpanRef,
y,
x,
this.bounds.Y,
this.maxY,
this.bounds.X,
this.maxX);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
}
}
}

37
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs

@ -105,14 +105,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
ParallelRowIterator.IterateRows(
Rectangle.FromLTRB(minX, minY, maxX, maxY),
this.Configuration,
new RowIntervalAction(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX));
new RowAction(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX));
}
}
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="EdgeDetectorCompassProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction
private readonly struct RowAction : IRowAction
{
private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> passPixels;
@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly int shiftX;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> passPixels,
int minX,
@ -140,29 +140,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
{
int offsetY = y - this.shiftY;
int offsetY = y - this.shiftY;
ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(offsetY));
ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(offsetY));
ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(offsetY));
ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(offsetY));
for (int x = this.minX; x < this.maxX; x++)
{
int offsetX = x - this.shiftX;
for (int x = this.minX; x < this.maxX; x++)
{
int offsetX = x - this.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.FromVector4(pixelValue);
}
currentTargetPixel.FromVector4(pixelValue);
}
}
}

18
src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs

@ -4,7 +4,6 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Drawing
@ -102,13 +101,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
ParallelRowIterator.IterateRows(
workingRect,
configuration,
new RowIntervalAction(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity));
new RowAction(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity));
}
/// <summary>
/// A <see langword="struct"/> implementing the draw logic for <see cref="DrawImageProcessor{TPixelBg,TPixelFg}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction
private readonly struct RowAction : IRowAction
{
private readonly ImageFrame<TPixelBg> sourceFrame;
private readonly Image<TPixelFg> targetImage;
@ -121,7 +120,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
private readonly float opacity;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
ImageFrame<TPixelBg> sourceFrame,
Image<TPixelFg> targetImage,
PixelBlender<TPixelBg> blender,
@ -145,14 +144,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixelBg> background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width);
Span<TPixelFg> foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width);
this.blender.Blend<TPixelFg>(this.configuration, background, background, foreground, this.opacity);
}
Span<TPixelBg> background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width);
Span<TPixelFg> foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width);
this.blender.Blend<TPixelFg>(this.configuration, background, background, foreground, this.opacity);
}
}
}

95
src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs

@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
ParallelRowIterator.IterateRows(
this.SourceRectangle,
this.Configuration,
new RowIntervalAction(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels));
new RowAction(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels));
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="OilPaintingProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction
private readonly struct RowAction : IRowAction
{
private readonly Rectangle bounds;
private readonly Buffer2D<TPixel> targetPixels;
@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
private readonly int levels;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
ImageFrame<TPixel> source,
@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
int maxY = this.bounds.Bottom - 1;
int maxX = this.bounds.Right - 1;
@ -117,69 +117,66 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
ref float blueBinRef = ref Unsafe.Add(ref redBinRef, this.levels);
ref float greenBinRef = ref Unsafe.Add(ref blueBinRef, this.levels);
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRowPixelSpan = this.source.GetPixelRowSpan(y);
Span<TPixel> sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width);
Span<TPixel> sourceRowPixelSpan = this.source.GetPixelRowSpan(y);
Span<TPixel> sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span);
for (int x = this.bounds.X; x < this.bounds.Right; x++)
{
int maxIntensity = 0;
int maxIndex = 0;
for (int x = this.bounds.X; x < this.bounds.Right; x++)
{
int maxIntensity = 0;
int maxIndex = 0;
// Clear the current shared buffer before processing each target pixel
bins.Memory.Span.Clear();
// Clear the current shared buffer before processing each target pixel
bins.Memory.Span.Clear();
for (int fy = 0; fy <= this.radius; fy++)
{
int fyr = fy - this.radius;
int offsetY = y + fyr;
for (int fy = 0; fy <= this.radius; fy++)
{
int fyr = fy - this.radius;
int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY);
offsetY = offsetY.Clamp(0, maxY);
Span<TPixel> sourceOffsetRow = this.source.GetPixelRowSpan(offsetY);
Span<TPixel> sourceOffsetRow = this.source.GetPixelRowSpan(offsetY);
for (int fx = 0; fx <= this.radius; fx++)
{
int fxr = fx - this.radius;
int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX);
for (int fx = 0; fx <= this.radius; fx++)
{
int fxr = fx - this.radius;
int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX);
var vector = sourceOffsetRow[offsetX].ToVector4();
var vector = sourceOffsetRow[offsetX].ToVector4();
float sourceRed = vector.X;
float sourceBlue = vector.Z;
float sourceGreen = vector.Y;
float sourceRed = vector.X;
float sourceBlue = vector.Z;
float sourceGreen = vector.Y;
int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1));
int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1));
Unsafe.Add(ref intensityBinRef, currentIntensity)++;
Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed;
Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue;
Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen;
Unsafe.Add(ref intensityBinRef, currentIntensity)++;
Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed;
Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue;
Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen;
if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity)
{
maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity);
maxIndex = currentIntensity;
}
if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity)
{
maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity);
maxIndex = currentIntensity;
}
}
float red = MathF.Abs(Unsafe.Add(ref redBinRef, maxIndex) / maxIntensity);
float blue = MathF.Abs(Unsafe.Add(ref blueBinRef, maxIndex) / maxIntensity);
float green = MathF.Abs(Unsafe.Add(ref greenBinRef, maxIndex) / maxIntensity);
float alpha = sourceRowVector4Span[x].W;
float red = MathF.Abs(Unsafe.Add(ref redBinRef, maxIndex) / maxIntensity);
float blue = MathF.Abs(Unsafe.Add(ref blueBinRef, maxIndex) / maxIntensity);
float green = MathF.Abs(Unsafe.Add(ref greenBinRef, maxIndex) / maxIntensity);
float alpha = sourceRowVector4Span[x].W;
targetRowVector4Span[x] = new Vector4(red, green, blue, alpha);
}
targetRowVector4Span[x] = new Vector4(red, green, blue, alpha);
}
}
Span<TPixel> targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
Span<TPixel> targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan);
}
}
}

27
src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs

@ -5,7 +5,6 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Effects
@ -51,16 +50,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
{
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
ParallelRowIterator.IterateRows<RowIntervalAction, Vector4>(
ParallelRowIterator.IterateRows<RowAction, Vector4>(
interest,
this.Configuration,
new RowIntervalAction(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate));
new RowAction(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate));
}
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="PixelRowDelegateProcessor{TPixel,TDelegate}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction<Vector4>
private readonly struct RowAction : IRowAction<Vector4>
{
private readonly int startX;
private readonly ImageFrame<TPixel> source;
@ -69,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
private readonly TDelegate rowProcessor;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
int startX,
ImageFrame<TPixel> source,
Configuration configuration,
@ -85,20 +84,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Memory<Vector4> memory)
public void Invoke(int y, Span<Vector4> span)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<Vector4> vectorSpan = memory.Span;
int length = vectorSpan.Length;
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, rowSpan, vectorSpan, this.modifiers);
int length = span.Length;
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers);
// Run the user defined pixel shader to the current row of pixels
Unsafe.AsRef(this.rowProcessor).Invoke(vectorSpan, new Point(this.startX, y));
// Run the user defined pixel shader to the current row of pixels
Unsafe.AsRef(this.rowProcessor).Invoke(span, new Point(this.startX, y));
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan, this.modifiers);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span, rowSpan, this.modifiers);
}
}
}

25
src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs

@ -5,7 +5,6 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Filters
@ -37,16 +36,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
{
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
ParallelRowIterator.IterateRows<RowIntervalAction, Vector4>(
ParallelRowIterator.IterateRows<RowAction, Vector4>(
interest,
this.Configuration,
new RowIntervalAction(interest.X, source, this.definition.Matrix, this.Configuration));
new RowAction(interest.X, source, this.definition.Matrix, this.Configuration));
}
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="FilterProcessor{TPixel}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction<Vector4>
private readonly struct RowAction : IRowAction<Vector4>
{
private readonly int startX;
private readonly ImageFrame<TPixel> source;
@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
private readonly Configuration configuration;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
int startX,
ImageFrame<TPixel> source,
ColorMatrix matrix,
@ -68,19 +67,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Memory<Vector4> memory)
public void Invoke(int y, Span<Vector4> span)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<Vector4> vectorSpan = memory.Span;
int length = vectorSpan.Length;
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, rowSpan, vectorSpan);
int length = span.Length;
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, rowSpan, span);
Vector4Utils.Transform(vectorSpan, ref Unsafe.AsRef(this.matrix));
Vector4Utils.Transform(span, ref Unsafe.AsRef(this.matrix));
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span, rowSpan);
}
}
}

46
src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs

@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
ParallelRowIterator.IterateRows(
workingRect,
this.Configuration,
new GrayscaleLevelsRowIntervalAction(workingRect, histogramBuffer, source, this.LuminanceLevels));
new GrayscaleLevelsRowAction(workingRect, histogramBuffer, source, this.LuminanceLevels));
Span<int> histogram = histogramBuffer.GetSpan();
if (this.ClipHistogramEnabled)
@ -77,13 +77,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
ParallelRowIterator.IterateRows(
workingRect,
this.Configuration,
new CdfApplicationRowIntervalAction(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin));
new CdfApplicationRowAction(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin));
}
/// <summary>
/// A <see langword="struct"/> implementing the grayscale levels logic for <see cref="GlobalHistogramEqualizationProcessor{TPixel}"/>.
/// </summary>
private readonly struct GrayscaleLevelsRowIntervalAction : IRowIntervalAction
private readonly struct GrayscaleLevelsRowAction : IRowAction
{
private readonly Rectangle bounds;
private readonly IMemoryOwner<int> histogramBuffer;
@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
private readonly int luminanceLevels;
[MethodImpl(InliningOptions.ShortMethod)]
public GrayscaleLevelsRowIntervalAction(
public GrayscaleLevelsRowAction(
in Rectangle bounds,
IMemoryOwner<int> histogramBuffer,
ImageFrame<TPixel> source,
@ -105,18 +105,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan());
for (int y = rows.Min; y < rows.Max; y++)
{
ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y));
ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y));
for (int x = 0; x < this.bounds.Width; x++)
{
int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels);
Unsafe.Add(ref histogramBase, luminance)++;
}
for (int x = 0; x < this.bounds.Width; x++)
{
int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels);
Unsafe.Add(ref histogramBase, luminance)++;
}
}
}
@ -124,7 +121,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// <summary>
/// A <see langword="struct"/> implementing the cdf application levels logic for <see cref="GlobalHistogramEqualizationProcessor{TPixel}"/>.
/// </summary>
private readonly struct CdfApplicationRowIntervalAction : IRowIntervalAction
private readonly struct CdfApplicationRowAction : IRowAction
{
private readonly Rectangle bounds;
private readonly IMemoryOwner<int> cdfBuffer;
@ -133,7 +130,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
private readonly float numberOfPixelsMinusCdfMin;
[MethodImpl(InliningOptions.ShortMethod)]
public CdfApplicationRowIntervalAction(
public CdfApplicationRowAction(
in Rectangle bounds,
IMemoryOwner<int> cdfBuffer,
ImageFrame<TPixel> source,
@ -149,20 +146,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan());
for (int y = rows.Min; y < rows.Max; y++)
ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y));
for (int x = 0; x < this.bounds.Width; x++)
{
ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y));
for (int x = 0; x < this.bounds.Width; x++)
{
ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x);
int luminance = GetLuminance(pixel, this.luminanceLevels);
float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin;
pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
}
ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x);
int luminance = GetLuminance(pixel, this.luminanceLevels);
float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin;
pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
}
}
}

31
src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs

@ -52,10 +52,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
ParallelRowIterator.IterateRows(
interest,
configuration,
new RowIntervalAction(configuration, interest, blender, amount, colors, source));
new RowAction(configuration, interest, blender, amount, colors, source));
}
private readonly struct RowIntervalAction : IRowIntervalAction
private readonly struct RowAction : IRowAction
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
private readonly ImageFrame<TPixel> source;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
Configuration configuration,
Rectangle bounds,
PixelBlender<TPixel> blender,
@ -82,23 +82,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> destination =
this.source.GetPixelRowSpan(y)
.Slice(this.bounds.X, this.bounds.Width);
Span<TPixel> destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
// Switch color & destination in the 2nd and 3rd places because we are
// applying the target color under the current one.
this.blender.Blend(
this.configuration,
destination,
this.colors.GetSpan(),
destination,
this.amount.GetSpan());
}
// Switch color & destination in the 2nd and 3rd places because we are
// applying the target color under the current one.
this.blender.Blend(
this.configuration,
destination,
this.colors.GetSpan(),
destination,
this.amount.GetSpan());
}
}
}

36
src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs

@ -55,13 +55,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
using IMemoryOwner<TPixel> rowColors = allocator.Allocate<TPixel>(interest.Width);
rowColors.GetSpan().Fill(glowColor);
ParallelRowIterator.IterateRows<RowIntervalAction, float>(
ParallelRowIterator.IterateRows<RowAction, float>(
interest,
configuration,
new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source));
new RowAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source));
}
private readonly struct RowIntervalAction : IRowIntervalAction<float>
private readonly struct RowAction : IRowAction<float>
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
private readonly ImageFrame<TPixel> source;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
Configuration configuration,
Rectangle bounds,
IMemoryOwner<TPixel> colors,
@ -94,28 +94,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Memory<float> memory)
public void Invoke(int y, Span<float> span)
{
Span<float> amountsSpan = memory.Span;
Span<TPixel> colorSpan = this.colors.GetSpan();
for (int y = rows.Min; y < rows.Max; y++)
for (int i = 0; i < this.bounds.Width; i++)
{
for (int i = 0; i < this.bounds.Width; i++)
{
float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y));
amountsSpan[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1);
}
float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y));
span[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1);
}
Span<TPixel> destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
Span<TPixel> destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
this.blender.Blend(
this.configuration,
destination,
destination,
colorSpan,
amountsSpan);
}
this.blender.Blend(
this.configuration,
destination,
destination,
colorSpan,
span);
}
}
}

38
src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs

@ -63,13 +63,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
using IMemoryOwner<TPixel> rowColors = allocator.Allocate<TPixel>(interest.Width);
rowColors.GetSpan().Fill(vignetteColor);
ParallelRowIterator.IterateRows<RowIntervalAction, float>(
ParallelRowIterator.IterateRows<RowAction, float>(
interest,
configuration,
new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source));
new RowAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source));
}
private readonly struct RowIntervalAction : IRowIntervalAction<float>
private readonly struct RowAction : IRowAction<float>
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
private readonly ImageFrame<TPixel> source;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
Configuration configuration,
Rectangle bounds,
IMemoryOwner<TPixel> colors,
@ -102,28 +102,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Memory<float> memory)
public void Invoke(int y, Span<float> span)
{
Span<float> amountsSpan = memory.Span;
Span<TPixel> colorSpan = this.colors.GetSpan();
for (int y = rows.Min; y < rows.Max; y++)
for (int i = 0; i < this.bounds.Width; i++)
{
for (int i = 0; i < this.bounds.Width; i++)
{
float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y));
amountsSpan[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1);
}
Span<TPixel> destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
this.blender.Blend(
this.configuration,
destination,
destination,
colorSpan,
amountsSpan);
float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y));
span[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1);
}
Span<TPixel> destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
this.blender.Blend(
this.configuration,
destination,
destination,
colorSpan,
span);
}
}
}

81
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs

@ -5,7 +5,6 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -61,23 +60,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ParallelRowIterator.IterateRows(
targetBounds,
configuration,
new NearestNeighborRowIntervalAction(this.SourceRectangle, ref matrix, width, source, destination));
new NearestNeighborRowAction(this.SourceRectangle, ref matrix, width, source, destination));
return;
}
using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler);
ParallelRowIterator.IterateRows<RowIntervalAction, Vector4>(
ParallelRowIterator.IterateRows<RowAction, Vector4>(
targetBounds,
configuration,
new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination));
new RowAction(configuration, kernelMap, ref matrix, width, source, destination));
}
/// <summary>
/// A <see langword="struct"/> implementing the nearest neighbor resampler logic for <see cref="AffineTransformProcessor{T}"/>.
/// </summary>
private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction
private readonly struct NearestNeighborRowAction : IRowAction
{
private readonly Rectangle bounds;
private readonly Matrix3x2 matrix;
@ -86,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public NearestNeighborRowIntervalAction(
public NearestNeighborRowAction(
Rectangle bounds,
ref Matrix3x2 matrix,
int maxX,
@ -101,21 +100,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
/// <inheritdoc/>
/// <param name="rows"></param>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> destRow = this.destination.GetPixelRowSpan(y);
Span<TPixel> destRow = this.destination.GetPixelRowSpan(y);
for (int x = 0; x < this.maxX; x++)
for (int x = 0; x < this.maxX; x++)
{
var point = Point.Transform(new Point(x, y), this.matrix);
if (this.bounds.Contains(point.X, point.Y))
{
var point = Point.Transform(new Point(x, y), this.matrix);
if (this.bounds.Contains(point.X, point.Y))
{
destRow[x] = this.source[point.X, point.Y];
}
destRow[x] = this.source[point.X, point.Y];
}
}
}
@ -124,7 +119,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary>
/// A <see langword="struct"/> implementing the transformation logic for <see cref="AffineTransformProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction<Vector4>
private readonly struct RowAction : IRowAction<Vector4>
{
private readonly Configuration configuration;
private readonly TransformKernelMap kernelMap;
@ -134,7 +129,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
Configuration configuration,
TransformKernelMap kernelMap,
ref Matrix3x2 matrix,
@ -152,35 +147,31 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Memory<Vector4> memory)
public void Invoke(int y, Span<Vector4> span)
{
Span<Vector4> vectorSpan = memory.Span;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = this.destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan, vectorSpan);
ref float ySpanRef = ref this.kernelMap.GetYStartReference(y);
ref float xSpanRef = ref this.kernelMap.GetXStartReference(y);
for (int x = 0; x < this.maxX; x++)
{
// 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), this.matrix);
this.kernelMap.Convolve(
point,
x,
ref ySpanRef,
ref xSpanRef,
this.source.PixelBuffer,
vectorSpan);
}
Span<TPixel> targetRowSpan = this.destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan, span);
ref float ySpanRef = ref this.kernelMap.GetYStartReference(y);
ref float xSpanRef = ref this.kernelMap.GetXStartReference(y);
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.configuration,
vectorSpan,
targetRowSpan);
for (int x = 0; x < this.maxX; x++)
{
// 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), this.matrix);
this.kernelMap.Convolve(
point,
x,
ref ySpanRef,
ref xSpanRef,
this.source.PixelBuffer,
span);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.configuration,
span,
targetRowSpan);
}
}
}

29
src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs

@ -4,7 +4,6 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -48,34 +47,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Rectangle bounds = this.cropRectangle;
// Copying is cheap, we should process more pixels per task:
ParallelExecutionSettings parallelSettings =
ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4);
var rowAction = new RowIntervalAction(ref bounds, source, destination);
ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4);
ParallelRowIterator.IterateRows(
bounds,
in parallelSettings,
in rowAction);
new RowAction(ref bounds, source, destination));
}
/// <summary>
/// A <see langword="struct"/> implementing the processor logic for <see cref="CropProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction
private readonly struct RowAction : IRowAction
{
private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
/// <summary>
/// Initializes a new instance of the <see cref="RowIntervalAction"/> struct.
/// </summary>
/// <param name="bounds">The target processing bounds for the current instance.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current instance.</param>
/// <param name="destination">The destination <see cref="Image{TPixel}"/> for the current instance.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(ref Rectangle bounds, ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
public RowAction(ref Rectangle bounds, ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{
this.bounds = bounds;
this.source = source;
@ -84,14 +74,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left);
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top);
sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow);
}
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left);
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top);
sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow);
}
}
}

17
src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs

@ -5,7 +5,6 @@ using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -79,23 +78,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ParallelRowIterator.IterateRows(
source.Bounds(),
configuration,
new RowIntervalAction(source));
new RowAction(source));
}
private readonly struct RowIntervalAction : IRowIntervalAction
/// <summary>
/// A <see langword="struct"/> implementing the reverse logic for <see cref="FlipProcessor{T}"/>.
/// </summary>
private readonly struct RowAction : IRowAction
{
private readonly ImageFrame<TPixel> source;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(ImageFrame<TPixel> source) => this.source = source;
public RowAction(ImageFrame<TPixel> source) => this.source = source;
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
{
this.source.GetPixelRowSpan(y).Reverse();
}
this.source.GetPixelRowSpan(y).Reverse();
}
}
}

96
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs

@ -5,7 +5,6 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -17,9 +16,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private Size targetSize;
private readonly Size targetSize;
private readonly IResampler resampler;
private Matrix4x4 transformMatrix;
private readonly Matrix4x4 transformMatrix;
/// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class.
@ -63,20 +62,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ParallelRowIterator.IterateRows(
targetBounds,
configuration,
new NearestNeighborRowIntervalAction(ref sourceBounds, ref matrix, width, source, destination));
new NearestNeighborRowAction(ref sourceBounds, ref matrix, width, source, destination));
return;
}
using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler);
ParallelRowIterator.IterateRows<RowIntervalAction, Vector4>(
ParallelRowIterator.IterateRows<RowAction, Vector4>(
targetBounds,
configuration,
new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination));
new RowAction(configuration, kernelMap, ref matrix, width, source, destination));
}
private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction
/// <summary>
/// A <see langword="struct"/> implementing the nearest neighbor interpolation logic for <see cref="ProjectiveTransformProcessor{T}"/>.
/// </summary>
private readonly struct NearestNeighborRowAction : IRowAction
{
private readonly Rectangle bounds;
private readonly Matrix4x4 matrix;
@ -85,7 +87,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public NearestNeighborRowIntervalAction(
public NearestNeighborRowAction(
ref Rectangle bounds,
ref Matrix4x4 matrix,
int maxX,
@ -99,29 +101,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.destination = destination;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
Span<TPixel> destRow = this.destination.GetPixelRowSpan(y);
for (int x = 0; x < this.maxX; x++)
{
Span<TPixel> destRow = this.destination.GetPixelRowSpan(y);
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix);
int px = (int)MathF.Round(point.X);
int py = (int)MathF.Round(point.Y);
for (int x = 0; x < this.maxX; x++)
if (this.bounds.Contains(px, py))
{
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix);
int px = (int)MathF.Round(point.X);
int py = (int)MathF.Round(point.Y);
if (this.bounds.Contains(px, py))
{
destRow[x] = this.source[px, py];
}
destRow[x] = this.source[px, py];
}
}
}
}
private readonly struct RowIntervalAction : IRowIntervalAction<Vector4>
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="ProjectiveTransformProcessor{T}"/>.
/// </summary>
private readonly struct RowAction : IRowAction<Vector4>
{
private readonly Configuration configuration;
private readonly TransformKernelMap kernelMap;
@ -131,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
Configuration configuration,
TransformKernelMap kernelMap,
ref Matrix4x4 matrix,
@ -147,36 +150,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.destination = destination;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Memory<Vector4> memory)
public void Invoke(int y, Span<Vector4> span)
{
Span<Vector4> vectorSpan = memory.Span;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = this.destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan, vectorSpan);
ref float ySpanRef = ref this.kernelMap.GetYStartReference(y);
ref float xSpanRef = ref this.kernelMap.GetXStartReference(y);
Span<TPixel> targetRowSpan = this.destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan, span);
ref float ySpanRef = ref this.kernelMap.GetYStartReference(y);
ref float xSpanRef = ref this.kernelMap.GetXStartReference(y);
for (int x = 0; x < this.maxX; x++)
{
// Use the single precision position to calculate correct bounding pixels
// otherwise we get rogue pixels outside of the bounds.
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix);
this.kernelMap.Convolve(
point,
x,
ref ySpanRef,
ref xSpanRef,
this.source.PixelBuffer,
vectorSpan);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.configuration,
vectorSpan,
targetRowSpan);
for (int x = 0; x < this.maxX; x++)
{
// Use the single precision position to calculate correct bounding pixels
// otherwise we get rogue pixels outside of the bounds.
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix);
this.kernelMap.Convolve(
point,
x,
ref ySpanRef,
ref xSpanRef,
this.source.PixelBuffer,
span);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.configuration,
span,
targetRowSpan);
}
}
}

25
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ParallelRowIterator.IterateRows(
interest,
configuration,
new RowIntervalAction(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination));
new RowAction(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination));
return;
}
@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
base.Dispose(disposing);
}
private readonly struct RowIntervalAction : IRowIntervalAction
private readonly struct RowAction : IRowAction
{
private readonly Rectangle sourceBounds;
private readonly Rectangle destinationBounds;
@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
Rectangle sourceBounds,
Rectangle destinationBounds,
float widthFactor,
@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
int sourceX = this.sourceBounds.X;
int sourceY = this.sourceBounds.Y;
@ -183,17 +183,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int destLeft = this.destinationBounds.Left;
int destRight = this.destinationBounds.Right;
for (int y = rows.Min; y < rows.Max; y++)
// Y coordinates of source points
Span<TPixel> sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY));
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(y);
for (int x = destLeft; x < destRight; x++)
{
// Y coordinates of source points
Span<TPixel> sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY));
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(y);
for (int x = destLeft; x < destRight; x++)
{
// X coordinates of source points
targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)];
}
// X coordinates of source points
targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)];
}
}
}

69
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs

@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ParallelRowIterator.IterateRows(
source.Bounds(),
configuration,
new Rotate180RowIntervalAction(source.Width, source.Height, source, destination));
new Rotate180RowAction(source.Width, source.Height, source, destination));
}
/// <summary>
@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ParallelRowIterator.IterateRows(
source.Bounds(),
configuration,
new Rotate270RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination));
new Rotate270RowAction(destination.Bounds(), source.Width, source.Height, source, destination));
}
/// <summary>
@ -162,10 +162,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ParallelRowIterator.IterateRows(
source.Bounds(),
configuration,
new Rotate90RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination));
new Rotate90RowAction(destination.Bounds(), source.Width, source.Height, source, destination));
}
private readonly struct Rotate180RowIntervalAction : IRowIntervalAction
private readonly struct Rotate180RowAction : IRowAction
{
private readonly int width;
private readonly int height;
@ -173,7 +173,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public Rotate180RowIntervalAction(
public Rotate180RowAction(
int width,
int height,
ImageFrame<TPixel> source,
@ -186,22 +186,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(this.height - y - 1);
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(this.height - y - 1);
for (int x = 0; x < this.width; x++)
{
targetRow[this.width - x - 1] = sourceRow[x];
}
for (int x = 0; x < this.width; x++)
{
targetRow[this.width - x - 1] = sourceRow[x];
}
}
}
private readonly struct Rotate270RowIntervalAction : IRowIntervalAction
private readonly struct Rotate270RowAction : IRowAction
{
private readonly Rectangle bounds;
private readonly int width;
@ -210,7 +207,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public Rotate270RowIntervalAction(
public Rotate270RowAction(
Rectangle bounds,
int width,
int height,
@ -225,27 +222,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
for (int x = 0; x < this.width; x++)
{
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
for (int x = 0; x < this.width; x++)
int newX = this.height - y - 1;
newX = this.height - newX - 1;
int newY = this.width - x - 1;
if (this.bounds.Contains(newX, newY))
{
int newX = this.height - y - 1;
newX = this.height - newX - 1;
int newY = this.width - x - 1;
if (this.bounds.Contains(newX, newY))
{
this.destination[newX, newY] = sourceRow[x];
}
this.destination[newX, newY] = sourceRow[x];
}
}
}
}
private readonly struct Rotate90RowIntervalAction : IRowIntervalAction
private readonly struct Rotate90RowAction : IRowAction
{
private readonly Rectangle bounds;
private readonly int width;
@ -254,7 +248,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public Rotate90RowIntervalAction(
public Rotate90RowAction(
Rectangle bounds,
int width,
int height,
@ -269,18 +263,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
int newX = this.height - y - 1;
for (int x = 0; x < this.width; x++)
{
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
int newX = this.height - y - 1;
for (int x = 0; x < this.width; x++)
if (this.bounds.Contains(newX, x))
{
if (this.bounds.Contains(newX, x))
{
this.destination[newX, x] = sourceRow[x];
}
this.destination[newX, x] = sourceRow[x];
}
}
}

Loading…
Cancel
Save