|
|
|
@ -3,10 +3,10 @@ |
|
|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
using System.Threading.Tasks; |
|
|
|
|
|
|
|
using SixLabors.ImageSharp.Advanced; |
|
|
|
using SixLabors.ImageSharp.Common; |
|
|
|
using SixLabors.ImageSharp.PixelFormats; |
|
|
|
|
|
|
|
namespace SixLabors.ImageSharp.Processing.Processors.Effects |
|
|
|
@ -38,77 +38,69 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects |
|
|
|
/// <inheritdoc/>
|
|
|
|
protected override void OnFrameApply(ImageFrame<TPixel> source) |
|
|
|
{ |
|
|
|
if (this.Size <= 0 || this.Size > source.Height || this.Size > source.Width) |
|
|
|
{ |
|
|
|
throw new ArgumentOutOfRangeException(nameof(this.Size)); |
|
|
|
} |
|
|
|
|
|
|
|
int startY = this.SourceRectangle.Y; |
|
|
|
int endY = this.SourceRectangle.Bottom; |
|
|
|
int startX = this.SourceRectangle.X; |
|
|
|
int endX = this.SourceRectangle.Right; |
|
|
|
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); |
|
|
|
int size = this.Size; |
|
|
|
int offset = this.Size / 2; |
|
|
|
|
|
|
|
// Align start/end positions.
|
|
|
|
int minX = Math.Max(0, startX); |
|
|
|
int maxX = Math.Min(source.Width, endX); |
|
|
|
int minY = Math.Max(0, startY); |
|
|
|
int maxY = Math.Min(source.Height, endY); |
|
|
|
Guard.MustBeBetweenOrEqualTo(size, 0, interest.Width, nameof(size)); |
|
|
|
Guard.MustBeBetweenOrEqualTo(size, 0, interest.Height, nameof(size)); |
|
|
|
|
|
|
|
// Get the range on the y-plane to choose from.
|
|
|
|
// TODO: It would be nice to be able to pool this somehow but neither Memory<T> nor Span<T>
|
|
|
|
// implement IEnumerable<T>.
|
|
|
|
IEnumerable<int> range = EnumerableExtensions.SteppedRange(interest.Y, i => i < interest.Bottom, size); |
|
|
|
Parallel.ForEach( |
|
|
|
range, |
|
|
|
this.Configuration.GetParallelOptions(), |
|
|
|
new RowAction(interest, size, source).Invoke); |
|
|
|
} |
|
|
|
|
|
|
|
// Reset offset if necessary.
|
|
|
|
if (minX > 0) |
|
|
|
private readonly struct RowAction : IRowAction |
|
|
|
{ |
|
|
|
private readonly int minX; |
|
|
|
private readonly int maxX; |
|
|
|
private readonly int maxXIndex; |
|
|
|
private readonly int maxY; |
|
|
|
private readonly int maxYIndex; |
|
|
|
private readonly int size; |
|
|
|
private readonly int radius; |
|
|
|
private readonly ImageFrame<TPixel> source; |
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
public RowAction( |
|
|
|
Rectangle bounds, |
|
|
|
int size, |
|
|
|
ImageFrame<TPixel> source) |
|
|
|
{ |
|
|
|
startX = 0; |
|
|
|
this.minX = bounds.X; |
|
|
|
this.maxX = bounds.Right; |
|
|
|
this.maxXIndex = bounds.Right - 1; |
|
|
|
this.maxY = bounds.Bottom; |
|
|
|
this.maxYIndex = bounds.Bottom - 1; |
|
|
|
this.size = size; |
|
|
|
this.radius = size >> 1; |
|
|
|
this.source = source; |
|
|
|
} |
|
|
|
|
|
|
|
if (minY > 0) |
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
public void Invoke(int y) |
|
|
|
{ |
|
|
|
startY = 0; |
|
|
|
} |
|
|
|
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(Math.Min(y + this.radius, this.maxYIndex)); |
|
|
|
|
|
|
|
// Get the range on the y-plane to choose from.
|
|
|
|
IEnumerable<int> range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); |
|
|
|
for (int x = this.minX; x < this.maxX; x += this.size) |
|
|
|
{ |
|
|
|
// Get the pixel color in the centre of the soon to be pixelated area.
|
|
|
|
TPixel pixel = rowSpan[Math.Min(x + this.radius, this.maxXIndex)]; |
|
|
|
|
|
|
|
Parallel.ForEach( |
|
|
|
range, |
|
|
|
this.Configuration.GetParallelOptions(), |
|
|
|
y => |
|
|
|
// For each pixel in the pixelate size, set it to the centre color.
|
|
|
|
for (int oY = y; oY < y + this.size && oY < this.maxY; oY++) |
|
|
|
{ |
|
|
|
int offsetY = y - startY; |
|
|
|
int offsetPy = offset; |
|
|
|
|
|
|
|
// Make sure that the offset is within the boundary of the image.
|
|
|
|
while (offsetY + offsetPy >= maxY) |
|
|
|
{ |
|
|
|
offsetPy--; |
|
|
|
} |
|
|
|
|
|
|
|
Span<TPixel> row = source.GetPixelRowSpan(offsetY + offsetPy); |
|
|
|
|
|
|
|
for (int x = minX; x < maxX; x += size) |
|
|
|
for (int oX = x; oX < x + this.size && oX < this.maxX; oX++) |
|
|
|
{ |
|
|
|
int offsetX = x - startX; |
|
|
|
int offsetPx = offset; |
|
|
|
|
|
|
|
while (x + offsetPx >= maxX) |
|
|
|
{ |
|
|
|
offsetPx--; |
|
|
|
} |
|
|
|
|
|
|
|
// Get the pixel color in the centre of the soon to be pixelated area.
|
|
|
|
TPixel pixel = row[offsetX + offsetPx]; |
|
|
|
|
|
|
|
// For each pixel in the pixelate size, set it to the centre color.
|
|
|
|
for (int l = offsetY; l < offsetY + size && l < maxY; l++) |
|
|
|
{ |
|
|
|
for (int k = offsetX; k < offsetX + size && k < maxX; k++) |
|
|
|
{ |
|
|
|
source[k, l] = pixel; |
|
|
|
} |
|
|
|
} |
|
|
|
this.source[oX, oY] = pixel; |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|