diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index b34db8e19..4abaf7ac4 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/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 targetPixels = this.Configuration.MemoryAllocator.Allocate2D(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 sourceRowBuffer = configuration.MemoryAllocator.Allocate(rowWidth)) - using (IMemoryOwner targetRowBuffer = configuration.MemoryAllocator.Allocate(rowWidth)) - using (IMemoryOwner bins = configuration.MemoryAllocator.Allocate(levels * 4)) - { - Span sourceRowVector4Span = sourceRowBuffer.Memory.Span; - Span sourceRowAreaVector4Span = sourceRowVector4Span.Slice(startX, rectangleWidth); - - Span targetRowVector4Span = targetRowBuffer.Memory.Span; - Span 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(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 sourceRowPixelSpan = source.GetPixelRowSpan(y); - Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(startX, rectangleWidth); + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } - PixelOperations.Instance.ToVector4(configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly ImageFrame source; + private readonly Configuration configuration; + private readonly int radius; + private readonly int levels; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + Rectangle bounds, + Buffer2D targetPixels, + ImageFrame 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; + /// + [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 sourceRowBuffer = this.configuration.MemoryAllocator.Allocate(this.source.Width); + using IMemoryOwner targetRowBuffer = this.configuration.MemoryAllocator.Allocate(this.source.Width); + using IMemoryOwner bins = this.configuration.MemoryAllocator.Allocate(this.levels * 4); + + Span sourceRowVector4Span = sourceRowBuffer.Memory.Span; + Span sourceRowAreaVector4Span = sourceRowVector4Span.Slice(this.bounds.X, this.bounds.Width); + + Span targetRowVector4Span = targetRowBuffer.Memory.Span; + Span targetRowAreaVector4Span = targetRowVector4Span.Slice(this.bounds.X, this.bounds.Width); + + ref float binsRef = ref bins.GetReference(); + ref int intensityBinRef = ref Unsafe.As(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 sourceRowPixelSpan = this.source.GetPixelRowSpan(y); + Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); - // Clear the current shared buffer before processing each target pixel - bins.Memory.Span.Clear(); + PixelOperations.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 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 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 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.Instance.FromVector4Destructive(configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); + targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); } } - }); - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); + } + } } } }