|
|
|
@ -43,122 +43,144 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects |
|
|
|
throw new ArgumentOutOfRangeException(nameof(brushSize)); |
|
|
|
} |
|
|
|
|
|
|
|
int startY = this.SourceRectangle.Y; |
|
|
|
int endY = this.SourceRectangle.Bottom; |
|
|
|
int startX = this.SourceRectangle.X; |
|
|
|
int endX = this.SourceRectangle.Right; |
|
|
|
int maxY = endY - 1; |
|
|
|
int maxX = endX - 1; |
|
|
|
|
|
|
|
int radius = brushSize >> 1; |
|
|
|
int levels = this.definition.Levels; |
|
|
|
int rowWidth = source.Width; |
|
|
|
int rectangleWidth = this.SourceRectangle.Width; |
|
|
|
|
|
|
|
Configuration configuration = this.Configuration; |
|
|
|
|
|
|
|
using Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size()); |
|
|
|
|
|
|
|
source.CopyTo(targetPixels); |
|
|
|
|
|
|
|
var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); |
|
|
|
ParallelRowIterator.IterateRows( |
|
|
|
workingRect, |
|
|
|
this.SourceRectangle, |
|
|
|
this.Configuration, |
|
|
|
(rows) => |
|
|
|
{ |
|
|
|
/* Allocate the two temporary Vector4 buffers, one for the source row and one for the target row. |
|
|
|
* The ParallelHelper.IterateRowsWithTempBuffers overload is not used in this case because |
|
|
|
* the two allocated buffers have a length equal to the width of the source image, |
|
|
|
* and not just equal to the width of the target rectangle to process. |
|
|
|
* Furthermore, there are two buffers being allocated in this case, so using that overload would |
|
|
|
* have still required the explicit allocation of the secondary buffer. |
|
|
|
* Similarly, one temporary float buffer is also allocated from the pool, and that is used |
|
|
|
* to create the target bins for all the color channels being processed. |
|
|
|
* This buffer is only rented once outside of the main processing loop, and its contents |
|
|
|
* are cleared for each loop iteration, to avoid the repeated allocation for each processed pixel. */ |
|
|
|
using (IMemoryOwner<Vector4> sourceRowBuffer = configuration.MemoryAllocator.Allocate<Vector4>(rowWidth)) |
|
|
|
using (IMemoryOwner<Vector4> targetRowBuffer = configuration.MemoryAllocator.Allocate<Vector4>(rowWidth)) |
|
|
|
using (IMemoryOwner<float> bins = configuration.MemoryAllocator.Allocate<float>(levels * 4)) |
|
|
|
{ |
|
|
|
Span<Vector4> sourceRowVector4Span = sourceRowBuffer.Memory.Span; |
|
|
|
Span<Vector4> sourceRowAreaVector4Span = sourceRowVector4Span.Slice(startX, rectangleWidth); |
|
|
|
|
|
|
|
Span<Vector4> targetRowVector4Span = targetRowBuffer.Memory.Span; |
|
|
|
Span<Vector4> targetRowAreaVector4Span = targetRowVector4Span.Slice(startX, rectangleWidth); |
|
|
|
new RowIntervalAction(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels)); |
|
|
|
|
|
|
|
ref float binsRef = ref bins.GetReference(); |
|
|
|
ref int intensityBinRef = ref Unsafe.As<float, int>(ref binsRef); |
|
|
|
ref float redBinRef = ref Unsafe.Add(ref binsRef, levels); |
|
|
|
ref float blueBinRef = ref Unsafe.Add(ref redBinRef, levels); |
|
|
|
ref float greenBinRef = ref Unsafe.Add(ref blueBinRef, levels); |
|
|
|
|
|
|
|
for (int y = rows.Min; y < rows.Max; y++) |
|
|
|
{ |
|
|
|
Span<TPixel> sourceRowPixelSpan = source.GetPixelRowSpan(y); |
|
|
|
Span<TPixel> sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(startX, rectangleWidth); |
|
|
|
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels); |
|
|
|
} |
|
|
|
|
|
|
|
PixelOperations<TPixel>.Instance.ToVector4(configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); |
|
|
|
/// <summary>
|
|
|
|
/// A <see langword="struct"/> implementing the convolution logic for <see cref="OilPaintingProcessor{T}"/>.
|
|
|
|
/// </summary>
|
|
|
|
private readonly struct RowIntervalAction : IRowIntervalAction |
|
|
|
{ |
|
|
|
private readonly Rectangle bounds; |
|
|
|
private readonly Buffer2D<TPixel> targetPixels; |
|
|
|
private readonly ImageFrame<TPixel> source; |
|
|
|
private readonly Configuration configuration; |
|
|
|
private readonly int radius; |
|
|
|
private readonly int levels; |
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
public RowIntervalAction( |
|
|
|
Rectangle bounds, |
|
|
|
Buffer2D<TPixel> targetPixels, |
|
|
|
ImageFrame<TPixel> source, |
|
|
|
Configuration configuration, |
|
|
|
int radius, |
|
|
|
int levels) |
|
|
|
{ |
|
|
|
this.bounds = bounds; |
|
|
|
this.targetPixels = targetPixels; |
|
|
|
this.source = source; |
|
|
|
this.configuration = configuration; |
|
|
|
this.radius = radius; |
|
|
|
this.levels = levels; |
|
|
|
} |
|
|
|
|
|
|
|
for (int x = startX; x < endX; x++) |
|
|
|
{ |
|
|
|
int maxIntensity = 0; |
|
|
|
int maxIndex = 0; |
|
|
|
/// <inheritdoc/>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
public void Invoke(in RowInterval rows) |
|
|
|
{ |
|
|
|
int maxY = this.bounds.Bottom - 1; |
|
|
|
int maxX = this.bounds.Right - 1; |
|
|
|
|
|
|
|
/* Allocate the two temporary Vector4 buffers, one for the source row and one for the target row. |
|
|
|
* The ParallelHelper.IterateRowsWithTempBuffers overload is not used in this case because |
|
|
|
* the two allocated buffers have a length equal to the width of the source image, |
|
|
|
* and not just equal to the width of the target rectangle to process. |
|
|
|
* Furthermore, there are two buffers being allocated in this case, so using that overload would |
|
|
|
* have still required the explicit allocation of the secondary buffer. |
|
|
|
* Similarly, one temporary float buffer is also allocated from the pool, and that is used |
|
|
|
* to create the target bins for all the color channels being processed. |
|
|
|
* This buffer is only rented once outside of the main processing loop, and its contents |
|
|
|
* are cleared for each loop iteration, to avoid the repeated allocation for each processed pixel. */ |
|
|
|
using IMemoryOwner<Vector4> sourceRowBuffer = this.configuration.MemoryAllocator.Allocate<Vector4>(this.source.Width); |
|
|
|
using IMemoryOwner<Vector4> targetRowBuffer = this.configuration.MemoryAllocator.Allocate<Vector4>(this.source.Width); |
|
|
|
using IMemoryOwner<float> bins = this.configuration.MemoryAllocator.Allocate<float>(this.levels * 4); |
|
|
|
|
|
|
|
Span<Vector4> sourceRowVector4Span = sourceRowBuffer.Memory.Span; |
|
|
|
Span<Vector4> sourceRowAreaVector4Span = sourceRowVector4Span.Slice(this.bounds.X, this.bounds.Width); |
|
|
|
|
|
|
|
Span<Vector4> targetRowVector4Span = targetRowBuffer.Memory.Span; |
|
|
|
Span<Vector4> targetRowAreaVector4Span = targetRowVector4Span.Slice(this.bounds.X, this.bounds.Width); |
|
|
|
|
|
|
|
ref float binsRef = ref bins.GetReference(); |
|
|
|
ref int intensityBinRef = ref Unsafe.As<float, int>(ref binsRef); |
|
|
|
ref float redBinRef = ref Unsafe.Add(ref binsRef, this.levels); |
|
|
|
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); |
|
|
|
|
|
|
|
// Clear the current shared buffer before processing each target pixel
|
|
|
|
bins.Memory.Span.Clear(); |
|
|
|
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); |
|
|
|
|
|
|
|
for (int fy = 0; fy <= radius; fy++) |
|
|
|
{ |
|
|
|
int fyr = fy - radius; |
|
|
|
int offsetY = y + fyr; |
|
|
|
for (int x = this.bounds.X; x < this.bounds.Right; x++) |
|
|
|
{ |
|
|
|
int maxIntensity = 0; |
|
|
|
int maxIndex = 0; |
|
|
|
|
|
|
|
offsetY = offsetY.Clamp(0, maxY); |
|
|
|
// Clear the current shared buffer before processing each target pixel
|
|
|
|
bins.Memory.Span.Clear(); |
|
|
|
|
|
|
|
Span<TPixel> sourceOffsetRow = source.GetPixelRowSpan(offsetY); |
|
|
|
for (int fy = 0; fy <= this.radius; fy++) |
|
|
|
{ |
|
|
|
int fyr = fy - this.radius; |
|
|
|
int offsetY = y + fyr; |
|
|
|
|
|
|
|
for (int fx = 0; fx <= radius; fx++) |
|
|
|
{ |
|
|
|
int fxr = fx - radius; |
|
|
|
int offsetX = x + fxr; |
|
|
|
offsetX = offsetX.Clamp(0, maxX); |
|
|
|
offsetY = offsetY.Clamp(0, maxY); |
|
|
|
|
|
|
|
var vector = sourceOffsetRow[offsetX].ToVector4(); |
|
|
|
Span<TPixel> sourceOffsetRow = this.source.GetPixelRowSpan(offsetY); |
|
|
|
|
|
|
|
float sourceRed = vector.X; |
|
|
|
float sourceBlue = vector.Z; |
|
|
|
float sourceGreen = vector.Y; |
|
|
|
for (int fx = 0; fx <= this.radius; fx++) |
|
|
|
{ |
|
|
|
int fxr = fx - this.radius; |
|
|
|
int offsetX = x + fxr; |
|
|
|
offsetX = offsetX.Clamp(0, maxX); |
|
|
|
|
|
|
|
int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); |
|
|
|
var vector = sourceOffsetRow[offsetX].ToVector4(); |
|
|
|
|
|
|
|
Unsafe.Add(ref intensityBinRef, currentIntensity)++; |
|
|
|
Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; |
|
|
|
Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; |
|
|
|
Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; |
|
|
|
float sourceRed = vector.X; |
|
|
|
float sourceBlue = vector.Z; |
|
|
|
float sourceGreen = vector.Y; |
|
|
|
|
|
|
|
if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) |
|
|
|
{ |
|
|
|
maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); |
|
|
|
maxIndex = currentIntensity; |
|
|
|
} |
|
|
|
} |
|
|
|
int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1)); |
|
|
|
|
|
|
|
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; |
|
|
|
Unsafe.Add(ref intensityBinRef, currentIntensity)++; |
|
|
|
Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; |
|
|
|
Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; |
|
|
|
Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; |
|
|
|
|
|
|
|
targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); |
|
|
|
if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) |
|
|
|
{ |
|
|
|
maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); |
|
|
|
maxIndex = currentIntensity; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Span<TPixel> targetRowAreaPixelSpan = targetPixels.GetRowSpan(y).Slice(startX, rectangleWidth); |
|
|
|
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; |
|
|
|
|
|
|
|
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); |
|
|
|
targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels); |
|
|
|
Span<TPixel> targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); |
|
|
|
|
|
|
|
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|