From d33061c61384f7339be205fd1b22992a241b4620 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 8 Feb 2020 21:29:06 +1100 Subject: [PATCH] Update pixelate processor --- .../Common/Extensions/EnumerableExtensions.cs | 14 +-- .../Effects/PixelateProcessor{TPixel}.cs | 112 ++++++++---------- 2 files changed, 57 insertions(+), 69 deletions(-) diff --git a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs index cff6e3b601..983a1eb8b3 100644 --- a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Common +namespace SixLabors.ImageSharp { /// /// Encapsulates a series of time saving extension methods to the interface. @@ -34,15 +34,11 @@ namespace SixLabors.ImageSharp.Common /// /// Generates a sequence of integral numbers within a specified range. /// - /// - /// The start index, inclusive. - /// + /// The start index, inclusive. /// /// A method that has one parameter and returns a calculating the end index. /// - /// - /// The incremental step. - /// + /// The incremental step. /// /// The that contains a range of sequential integral numbers. /// @@ -56,4 +52,4 @@ namespace SixLabors.ImageSharp.Common } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs index df85afc5eb..506cea8da4 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs @@ -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 /// protected override void OnFrameApply(ImageFrame 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 nor Span + // implement IEnumerable. + IEnumerable 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 source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowAction( + Rectangle bounds, + int size, + ImageFrame 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 rowSpan = this.source.GetPixelRowSpan(Math.Min(y + this.radius, this.maxYIndex)); - // Get the range on the y-plane to choose from. - IEnumerable 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 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; } - }); + } + } + } } } }