Browse Source

Refactor OilPaintingProcessor<TPixel>

af/octree-no-pixelmap
Sergio Pedri 6 years ago
parent
commit
391f242e19
  1. 200
      src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs

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

@ -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);
}
}
}
}
}

Loading…
Cancel
Save