From aaa5bb7e361f50bfe43057967078c087f59be57a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 5 Feb 2020 12:23:28 +1100 Subject: [PATCH 01/58] Add row action overload --- .../Advanced/ParallelUtils/IRowAction.cs | 37 +++++++++ .../Advanced/ParallelUtils/ParallelHelper.cs | 76 ++++++++++++++++++- .../Transforms/CropProcessor{TPixel}.cs | 55 +++++++++++--- 3 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs new file mode 100644 index 000000000..a0fd2aaf2 --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced.ParallelUtils +{ + /// + /// Defines the contract for. + /// + public interface IRowAction + { + /// + /// Invokes the method passing the row interval. + /// + /// The row interval. + void Invoke(in RowInterval rows); + } + + internal readonly struct WrappingRowAction : IRowAction + where T : struct, IRowAction + { + private readonly T action; + + public WrappingRowAction(ref T action) + { + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + this.action.Invoke(in rows); + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs index ecefadb08..6f9b98d1d 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs +++ b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs @@ -28,19 +28,86 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, parallelSettings, body); + IterateRows(rectangle, in parallelSettings, body); } /// /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// + /// The type of row action to perform. /// The . - /// The . + /// The to get the parallel settings from. /// The method body defining the iteration logic on a single . - public static void IterateRows( + public static void IterateRowsFast(Rectangle rectangle, Configuration configuration, ref T body) + where T : struct, IRowAction + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + + IterateRowsFast(rectangle, in parallelSettings, ref body); + } + + internal static void IterateRowsFast( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, - Action body) + ref T body) + where T : struct, IRowAction + { + ValidateRectangle(rectangle); + + int maxSteps = DivideCeil( + rectangle.Width * rectangle.Height, + parallelSettings.MinimumPixelsProcessedPerTask); + + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(rectangle.Top, rectangle.Bottom); + body.Invoke(in rows); + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + + var rowAction = new WrappingRowAction(ref body); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + i => + { + int yMin = top + (i * verticalStep); + + if (yMin >= bottom) + { + return; + } + + int yMax = Math.Min(yMin + verticalStep, bottom); + + var rows = new RowInterval(yMin, yMax); + + rowAction.Invoke(in rows); + }); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + /// The . + /// The . + /// The method body defining the iteration logic on a single . + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + Action body) { ValidateRectangle(rectangle); @@ -72,6 +139,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); var rows = new RowInterval(yMin, yMax); + body(rows); }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index a286e8fa2..ec78ba171 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -2,8 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -50,18 +52,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration) .MultiplyMinimumPixelsPerTask(4); - ParallelHelper.IterateRows( + // ParallelHelper.IterateRows( + // bounds, + // parallelSettings, + // rows => + // { + // for (int y = rows.Min; y < rows.Max; y++) + // { + // Span sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left); + // Span targetRow = destination.GetPixelRowSpan(y - bounds.Top); + // sourceRow.Slice(0, bounds.Width).CopyTo(targetRow); + // } + // }); + var rowAction = new RowAction(ref bounds, source, destination); + + ParallelHelper.IterateRowsFast( bounds, - parallelSettings, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left); - Span targetRow = destination.GetPixelRowSpan(y - bounds.Top); - sourceRow.Slice(0, bounds.Width).CopyTo(targetRow); - } - }); + in parallelSettings, + ref rowAction); + } + + private readonly struct RowAction : IRowAction + { + private readonly Rectangle bounds; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + public RowAction(ref Rectangle bounds, ImageFrame source, ImageFrame destination) + { + this.bounds = bounds; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); + Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); + sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); + } + } } } } From 45233668cd50aa171f0125fa9d13b4b17faeacf4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 5 Feb 2020 13:44:59 +1100 Subject: [PATCH 02/58] Reduce captuing allocation to 16 bytes. --- .../Advanced/ParallelUtils/IRowAction.cs | 37 ---------- .../ParallelUtils/IRowIntervalAction.cs | 68 +++++++++++++++++++ .../Advanced/ParallelUtils/ParallelHelper.cs | 24 ++----- .../Transforms/CropProcessor{TPixel}.cs | 14 +--- 4 files changed, 74 insertions(+), 69 deletions(-) delete mode 100644 src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs create mode 100644 src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs deleted file mode 100644 index a0fd2aaf2..000000000 --- a/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Advanced.ParallelUtils -{ - /// - /// Defines the contract for. - /// - public interface IRowAction - { - /// - /// Invokes the method passing the row interval. - /// - /// The row interval. - void Invoke(in RowInterval rows); - } - - internal readonly struct WrappingRowAction : IRowAction - where T : struct, IRowAction - { - private readonly T action; - - public WrappingRowAction(ref T action) - { - this.action = action; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - this.action.Invoke(in rows); - } - } -} diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs new file mode 100644 index 000000000..b178434ce --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced.ParallelUtils +{ + /// + /// Defines the contract for. + /// + public interface IRowIntervalAction + { + /// + /// Invokes the method passing the row interval. + /// + /// The row interval. + void Invoke(in RowInterval rows); + } + + internal readonly struct WrappingRowIntervalInfo + { + public readonly int Min; + public readonly int Max; + public readonly int Step; + + public WrappingRowIntervalInfo(int min, int max, int step) + { + this.Min = min; + this.Max = max; + this.Step = step; + } + } + + internal readonly struct WrappingRowIntervalAction : IRowIntervalAction + where T : struct, IRowIntervalAction + { + private readonly WrappingRowIntervalInfo info; + private readonly T action; + + public WrappingRowIntervalAction(in WrappingRowIntervalInfo info, ref T action) + { + this.info = info; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.info.Min + (i * this.info.Step); + + if (yMin >= this.info.Max) + { + return; + } + + int yMax = Math.Min(yMin + this.info.Step, this.info.Max); + + var rows = new RowInterval(yMin, yMax); + + this.Invoke(in rows); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) => this.action.Invoke(in rows); + } +} diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs index 1be8a638d..58f3169d1 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs +++ b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// The to get the parallel settings from. /// The method body defining the iteration logic on a single . public static void IterateRowsFast(Rectangle rectangle, Configuration configuration, ref T body) - where T : struct, IRowAction + where T : struct, IRowIntervalAction { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils Rectangle rectangle, in ParallelExecutionSettings parallelSettings, ref T body) - where T : struct, IRowAction + where T : struct, IRowIntervalAction { ValidateRectangle(rectangle); @@ -74,28 +74,14 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils int top = rectangle.Top; int bottom = rectangle.Bottom; - - var rowAction = new WrappingRowAction(ref body); + var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); + var rowAction = new WrappingRowIntervalAction(in rowInfo, ref body); Parallel.For( 0, numOfSteps, parallelOptions, - i => - { - int yMin = top + (i * verticalStep); - - if (yMin >= bottom) - { - return; - } - - int yMax = Math.Min(yMin + verticalStep, bottom); - - var rows = new RowInterval(yMin, yMax); - - rowAction.Invoke(in rows); - }); + i => rowAction.Invoke(i)); } /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index ec78ba171..59ef75b6e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -52,18 +52,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration) .MultiplyMinimumPixelsPerTask(4); - // ParallelHelper.IterateRows( - // bounds, - // parallelSettings, - // rows => - // { - // for (int y = rows.Min; y < rows.Max; y++) - // { - // Span sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left); - // Span targetRow = destination.GetPixelRowSpan(y - bounds.Top); - // sourceRow.Slice(0, bounds.Width).CopyTo(targetRow); - // } - // }); var rowAction = new RowAction(ref bounds, source, destination); ParallelHelper.IterateRowsFast( @@ -72,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ref rowAction); } - private readonly struct RowAction : IRowAction + private readonly struct RowAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly ImageFrame source; From 463f0a9c970037a03d540daa1735c61059f2dd98 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 5 Feb 2020 14:04:17 +1100 Subject: [PATCH 03/58] Add memorydiagnoser --- tests/ImageSharp.Benchmarks/Samplers/Crop.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs index 48f8dc166..8a5cccd68 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs @@ -13,6 +13,7 @@ using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks { + [Config(typeof(Config.ShortClr))] public class Crop : BenchmarkBase { [Benchmark(Baseline = true, Description = "System.Drawing Crop")] From abb064f2a0a95b3b6e0c0041df3f5cfa0c880dc3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Feb 2020 00:06:10 +1100 Subject: [PATCH 04/58] Normalize row action with buffer. --- .../ParallelUtils/IRowIntervalAction.cs | 31 +++-- .../IRowIntervalAction{TBuffer}.cs | 67 ++++++++++ .../Advanced/ParallelUtils/ParallelHelper.cs | 125 ++++++++++++++---- .../Transforms/CropProcessor{TPixel}.cs | 4 +- 4 files changed, 187 insertions(+), 40 deletions(-) create mode 100644 src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs index b178434ce..830fcf736 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs +++ b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Advanced.ParallelUtils { /// - /// Defines the contract for. + /// Defines the contract for an action that operates on a row interval. /// public interface IRowIntervalAction { @@ -21,15 +21,22 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils internal readonly struct WrappingRowIntervalInfo { - public readonly int Min; - public readonly int Max; - public readonly int Step; + public readonly int MinY; + public readonly int MaxY; + public readonly int StepY; + public readonly int MaxX; - public WrappingRowIntervalInfo(int min, int max, int step) + public WrappingRowIntervalInfo(int minY, int maxY, int stepY) + : this(minY, maxY, stepY, 0) { - this.Min = min; - this.Max = max; - this.Step = step; + } + + public WrappingRowIntervalInfo(int minY, int maxY, int stepY, int maxX) + { + this.MinY = minY; + this.MaxY = maxY; + this.StepY = stepY; + this.MaxX = maxX; } } @@ -39,7 +46,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils private readonly WrappingRowIntervalInfo info; private readonly T action; - public WrappingRowIntervalAction(in WrappingRowIntervalInfo info, ref T action) + public WrappingRowIntervalAction(in WrappingRowIntervalInfo info, in T action) { this.info = info; this.action = action; @@ -48,14 +55,14 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int i) { - int yMin = this.info.Min + (i * this.info.Step); + int yMin = this.info.MinY + (i * this.info.StepY); - if (yMin >= this.info.Max) + if (yMin >= this.info.MaxY) { return; } - int yMax = Math.Min(yMin + this.info.Step, this.info.Max); + int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); var rows = new RowInterval(yMin, yMax); diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs new file mode 100644 index 000000000..c0899ad3a --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced.ParallelUtils +{ + /// + /// Defines the contract for an action that operates on a row interval with a temporary buffer. + /// + /// The type of buffer elements. + public interface IRowIntervalAction + where TBuffer : unmanaged + { + /// + /// Invokes the method passing the row interval and a buffer. + /// + /// The row interval. + /// The contiguous region of memory. + void Invoke(in RowInterval rows, Memory memory); + } + + internal readonly struct WrappingRowIntervalAction : IRowIntervalAction + where T : struct, IRowIntervalAction + where TBuffer : unmanaged + { + private readonly WrappingRowIntervalInfo info; + private readonly MemoryAllocator allocator; + private readonly T action; + + public WrappingRowIntervalAction( + in WrappingRowIntervalInfo info, + MemoryAllocator allocator, + in T action) + { + this.info = info; + this.allocator = allocator; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.info.MinY + (i * this.info.StepY); + + if (yMin >= this.info.MaxY) + { + return; + } + + int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + + var rows = new RowInterval(yMin, yMax); + + using (IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX)) + { + this.Invoke(in rows, buffer.Memory); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) => this.action.Invoke(in rows, memory); + } +} diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs index 58f3169d1..92c2ff20f 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs +++ b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs @@ -21,14 +21,16 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// + /// The type of row action to perform. /// The . /// The to get the parallel settings from. /// The method body defining the iteration logic on a single . - public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) + [MethodImpl(InliningOptions.ShortMethod)] + public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) + where T : struct, IRowIntervalAction { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - - IterateRows(rectangle, in parallelSettings, body); + IterateRows(rectangle, in parallelSettings, in body); } /// @@ -36,52 +38,120 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// /// The type of row action to perform. /// The . - /// The to get the parallel settings from. + /// The . /// The method body defining the iteration logic on a single . - public static void IterateRowsFast(Rectangle rectangle, Configuration configuration, ref T body) - where T : struct, IRowIntervalAction - { - var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - - IterateRowsFast(rectangle, in parallelSettings, ref body); - } - - internal static void IterateRowsFast( + public static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, - ref T body) + in T body) where T : struct, IRowIntervalAction { ValidateRectangle(rectangle); - int maxSteps = DivideCeil( - rectangle.Width * rectangle.Height, - parallelSettings.MinimumPixelsProcessedPerTask); + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); // Avoid TPL overhead in this trivial case: if (numOfSteps == 1) { - var rows = new RowInterval(rectangle.Top, rectangle.Bottom); + var rows = new RowInterval(top, bottom); body.Invoke(in rows); return; } int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); + var rowAction = new WrappingRowIntervalAction(in rowInfo, in body); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + i => rowAction.Invoke(i)); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row action to perform. + /// The type of buffer elements. + /// The . + /// The to get the parallel settings from. + /// The method body defining the iteration logic on a single . + public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) + where T : struct, IRowIntervalAction + where TBuffer : unmanaged + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows(rectangle, in parallelSettings, in body); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + internal static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T body) + where T : struct, IRowIntervalAction + where TBuffer : unmanaged + { + ValidateRectangle(rectangle); int top = rectangle.Top; int bottom = rectangle.Bottom; - var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); - var rowAction = new WrappingRowIntervalAction(in rowInfo, ref body); + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(top, bottom); + using (IMemoryOwner buffer = allocator.Allocate(width)) + { + body.Invoke(rows, buffer.Memory); + } + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); + var rowAction = new WrappingRowIntervalAction(in rowInfo, allocator, in body); Parallel.For( 0, numOfSteps, parallelOptions, - i => rowAction.Invoke(i)); + i => + rowAction.Invoke(i)); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + /// The . + /// The to get the parallel settings from. + /// The method body defining the iteration logic on a single . + // [Obsolete("Use non-allocating generic versions instead.")] + public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + + IterateRows(rectangle, in parallelSettings, body); } /// @@ -90,10 +160,11 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// The . /// The . /// The method body defining the iteration logic on a single . + // [Obsolete("Use non-allocating generic versions instead.")] public static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action body) + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + Action body) { ValidateRectangle(rectangle); @@ -143,6 +214,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// Iterate through the rows of a rectangle in optimized batches defined by -s /// instantiating a temporary buffer for each invocation. /// + // [Obsolete("Use non-allocating generic versions instead.")] internal static void IterateRowsWithTempBuffer( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, @@ -204,6 +276,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// Iterate through the rows of a rectangle in optimized batches defined by -s /// instantiating a temporary buffer for each invocation. /// + // [Obsolete("Use non-allocating generic versions instead.")] internal static void IterateRowsWithTempBuffer( Rectangle rectangle, Configuration configuration, @@ -213,7 +286,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils IterateRowsWithTempBuffer(rectangle, ParallelExecutionSettings.FromConfiguration(configuration), body); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); private static void ValidateRectangle(Rectangle rectangle) diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 59ef75b6e..8d3fec97d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -54,10 +54,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms var rowAction = new RowAction(ref bounds, source, destination); - ParallelHelper.IterateRowsFast( + ParallelHelper.IterateRows( bounds, in parallelSettings, - ref rowAction); + in rowAction); } private readonly struct RowAction : IRowIntervalAction From e3a7bb40c55fa95a1ca44108a6ec6cbfd76ae346 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Feb 2020 09:32:56 +1100 Subject: [PATCH 05/58] Remove final allocations --- .../Advanced/ParallelUtils/IRowIntervalAction.cs | 8 ++------ .../Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs | 8 ++------ src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs | 5 ++--- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs index 830fcf736..cb38b89bd 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs +++ b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils } } - internal readonly struct WrappingRowIntervalAction : IRowIntervalAction + internal readonly struct WrappingRowIntervalAction where T : struct, IRowIntervalAction { private readonly WrappingRowIntervalInfo info; @@ -63,13 +63,9 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils } int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); - var rows = new RowInterval(yMin, yMax); - this.Invoke(in rows); + this.action.Invoke(in rows); } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) => this.action.Invoke(in rows); } } diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs index c0899ad3a..6943405cd 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs +++ b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils void Invoke(in RowInterval rows, Memory memory); } - internal readonly struct WrappingRowIntervalAction : IRowIntervalAction + internal readonly struct WrappingRowIntervalAction where T : struct, IRowIntervalAction where TBuffer : unmanaged { @@ -52,16 +52,12 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils } int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); - var rows = new RowInterval(yMin, yMax); using (IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX)) { - this.Invoke(in rows, buffer.Memory); + this.action.Invoke(in rows, buffer.Memory); } } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) => this.action.Invoke(in rows, memory); } } diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs index 92c2ff20f..ea975a0ee 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs +++ b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils 0, numOfSteps, parallelOptions, - i => rowAction.Invoke(i)); + rowAction.Invoke); } /// @@ -136,8 +136,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils 0, numOfSteps, parallelOptions, - i => - rowAction.Invoke(i)); + rowAction.Invoke); } /// From 62890f81846b744284f3ef7904ad7395bab475a3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Feb 2020 17:16:48 +1100 Subject: [PATCH 06/58] Rename class and add native memory profiler --- .../{ParallelUtils => }/IRowIntervalAction.cs | 3 ++- .../IRowIntervalAction{TBuffer}.cs | 9 ++++--- .../ParallelExecutionSettings.cs | 8 +++---- ...rallelHelper.cs => ParallelRowIterator.cs} | 4 ++-- src/ImageSharp/ImageFrame{TPixel}.cs | 4 +--- src/ImageSharp/ImageSharp.csproj | 6 ++--- .../BinaryThresholdProcessor{TPixel}.cs | 4 +--- .../Convolution/BokehBlurProcessor{TPixel}.cs | 11 ++++----- .../Convolution2DProcessor{TPixel}.cs | 5 ++-- .../Convolution2PassProcessor{TPixel}.cs | 5 ++-- .../ConvolutionProcessor{TPixel}.cs | 5 ++-- .../EdgeDetectorCompassProcessor{TPixel}.cs | 5 ++-- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 4 +--- .../Effects/OilPaintingProcessor{TPixel}.cs | 4 +--- .../PixelRowDelegateProcessorBase{TPixel}.cs | 3 +-- .../Filters/FilterProcessor{TPixel}.cs | 3 +-- ...lHistogramEqualizationProcessor{TPixel}.cs | 6 ++--- .../BackgroundColorProcessor{TPixel}.cs | 4 +--- .../Overlays/GlowProcessor{TPixel}.cs | 4 +--- .../Overlays/VignetteProcessor{TPixel}.cs | 4 +--- .../AffineTransformProcessor{TPixel}.cs | 5 ++-- .../Transforms/CropProcessor{TPixel}.cs | 3 +-- .../Transforms/FlipProcessor{TPixel}.cs | 4 +--- .../ProjectiveTransformProcessor{TPixel}.cs | 6 ++--- .../Resize/ResizeProcessor{TPixel}.cs | 3 +-- .../Transforms/RotateProcessor{TPixel}.cs | 8 +++---- tests/Directory.Build.targets | 1 + tests/ImageSharp.Benchmarks/Config.cs | 22 +++++++++++++++++ .../ImageSharp.Benchmarks.csproj | 1 + .../Helpers/ParallelExecutionSettingsTests.cs | 2 +- ...erTests.cs => ParallelRowIteratorTests.cs} | 24 +++++++++---------- .../TestUtilities/TestImageExtensions.cs | 3 +-- 32 files changed, 87 insertions(+), 96 deletions(-) rename src/ImageSharp/Advanced/{ParallelUtils => }/IRowIntervalAction.cs (95%) rename src/ImageSharp/Advanced/{ParallelUtils => }/IRowIntervalAction{TBuffer}.cs (88%) rename src/ImageSharp/Advanced/{ParallelUtils => }/ParallelExecutionSettings.cs (95%) rename src/ImageSharp/Advanced/{ParallelUtils/ParallelHelper.cs => ParallelRowIterator.cs} (99%) rename tests/ImageSharp.Tests/Helpers/{ParallelHelperTests.cs => ParallelRowIteratorTests.cs} (94%) diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs b/src/ImageSharp/Advanced/IRowIntervalAction.cs similarity index 95% rename from src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs rename to src/ImageSharp/Advanced/IRowIntervalAction.cs index cb38b89bd..df422a65f 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs +++ b/src/ImageSharp/Advanced/IRowIntervalAction.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Advanced.ParallelUtils +namespace SixLabors.ImageSharp.Advanced { /// /// Defines the contract for an action that operates on a row interval. @@ -46,6 +46,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils private readonly WrappingRowIntervalInfo info; private readonly T action; + [MethodImpl(InliningOptions.ShortMethod)] public WrappingRowIntervalAction(in WrappingRowIntervalInfo info, in T action) { this.info = info; diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs similarity index 88% rename from src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs rename to src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs index 6943405cd..7b841b9cd 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Advanced.ParallelUtils +namespace SixLabors.ImageSharp.Advanced { /// /// Defines the contract for an action that operates on a row interval with a temporary buffer. @@ -31,6 +31,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils private readonly MemoryAllocator allocator; private readonly T action; + [MethodImpl(InliningOptions.ShortMethod)] public WrappingRowIntervalAction( in WrappingRowIntervalInfo info, MemoryAllocator allocator, @@ -54,10 +55,8 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); var rows = new RowInterval(yMin, yMax); - using (IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX)) - { - this.action.Invoke(in rows, buffer.Memory); - } + using IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX); + this.action.Invoke(in rows, buffer.Memory); } } } diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs similarity index 95% rename from src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs rename to src/ImageSharp/Advanced/ParallelExecutionSettings.cs index f17d70a2a..54ee06918 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs +++ b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,10 +6,10 @@ using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Advanced.ParallelUtils +namespace SixLabors.ImageSharp.Advanced { /// - /// Defines execution settings for methods in . + /// Defines execution settings for methods in . /// public readonly struct ParallelExecutionSettings { @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils } /// - /// Get the default for a + /// Get the default for a /// /// The . /// The . diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs similarity index 99% rename from src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs rename to src/ImageSharp/Advanced/ParallelRowIterator.cs index ea975a0ee..3bc9e1f90 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Advanced.ParallelUtils +namespace SixLabors.ImageSharp.Advanced { /// /// Utility methods for batched processing of pixel row intervals. @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// or . /// Using this class is preferred over direct usage of utility methods. /// - public static class ParallelHelper + public static class ParallelRowIterator { /// /// Iterate through the rows of a rectangle in optimized batches defined by -s. diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index e1112c017..88591af69 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -4,9 +4,7 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -262,7 +260,7 @@ namespace SixLabors.ImageSharp var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( this.Bounds(), configuration, rows => diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 0fd449d90..be0e9032b 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -24,9 +24,9 @@ - - - + + + diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 380ce64d2..9bc7f47b1 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Binarization @@ -51,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( workingRect, configuration, rows => diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 316579da7..bb89f0318 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -7,8 +7,7 @@ using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; @@ -342,7 +341,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); int width = workingRectangle.Width; - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( workingRectangle, configuration, rows => @@ -389,7 +388,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); int width = workingRectangle.Width; - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( workingRectangle, configuration, rows => @@ -428,7 +427,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int width = workingRectangle.Width; float exp = this.gamma; - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( workingRectangle, configuration, (rows, vectorBuffer) => @@ -479,7 +478,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int width = workingRectangle.Width; float expGamma = 1 / this.gamma; - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( workingRectangle, configuration, rows => diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index c2b85a4ab..431e1c604 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -4,8 +4,7 @@ using System; using System.Numerics; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -79,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); int width = workingRectangle.Width; - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( workingRectangle, this.Configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 32bdf6bc5..ce13b4074 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -4,8 +4,7 @@ using System; using System.Numerics; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -98,7 +97,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); int width = workingRectangle.Width; - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( workingRectangle, configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 285bcab27..faffbd575 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -4,8 +4,7 @@ using System; using System.Numerics; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -69,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); int width = workingRectangle.Width; - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( workingRectangle, this.Configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index c1897bed8..185142758 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -5,8 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; @@ -109,7 +108,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Buffer2D passPixels = pass.PixelBuffer; Buffer2D targetPixels = source.PixelBuffer; - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( workingRect, this.Configuration, rows => diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index a8b9093e5..e435013ad 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Drawing @@ -99,7 +97,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing "Cannot draw image because the source image does not overlap the target image."); } - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( workingRect, configuration, rows => diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 472c07aa7..b34db8e19 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -5,9 +5,7 @@ using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -63,7 +61,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects source.CopyTo(targetPixels); var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( workingRect, this.Configuration, (rows) => diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs index 019509dc2..bca288783 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Numerics; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -40,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects Configuration configuration = this.Configuration; PixelConversionModifiers modifiers = this.modifiers; - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( interest, this.Configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index 28a5837de..64d705a2f 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Numerics; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Filters @@ -39,7 +38,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters ColorMatrix matrix = this.definition.Matrix; - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( interest, this.Configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index ff34457fb..ff68d0049 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -6,9 +6,7 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -55,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) { // Build the histogram of the grayscale levels. - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( workingRect, this.Configuration, rows => @@ -88,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; // Apply the cdf to each pixel of the image - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( workingRect, this.Configuration, rows => diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index c4fabead2..7423fdbfe 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -3,9 +3,7 @@ using System; using System.Buffers; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -79,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( workingRect, configuration, rows => diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 363e670d0..0032a7983 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -4,9 +4,7 @@ using System; using System.Buffers; using System.Numerics; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -83,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays { rowColors.GetSpan().Fill(glowColor); - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( workingRect, configuration, (rows, amounts) => diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index 3e037189d..95fe35b09 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -4,9 +4,7 @@ using System; using System.Buffers; using System.Numerics; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -87,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays { rowColors.GetSpan().Fill(vignetteColor); - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( workingRect, configuration, (rows, amounts) => diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 1b9ff82bf..11f8719ef 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Numerics; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -58,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( targetBounds, configuration, rows => @@ -85,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms try { - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( targetBounds, configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 8d3fec97d..6baea69ed 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms var rowAction = new RowAction(ref bounds, source, destination); - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( bounds, in parallelSettings, in rowAction); diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs index d3afc7205..c01cdd8ef 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs @@ -3,9 +3,7 @@ using System; using System.Buffers; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -79,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void FlipY(ImageFrame source, Configuration configuration) { - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( source.Bounds(), configuration, rows => diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 56df606a7..b63e7eff3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -3,9 +3,7 @@ using System; using System.Numerics; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -59,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( targetBounds, configuration, rows => @@ -89,7 +87,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms try { - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( targetBounds, configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 2e94f88ac..5cfbbcb48 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -4,7 +4,6 @@ using System; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -101,7 +100,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( targetWorkingRect, configuration, rows => diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs index 8f1cf28ce..142068a48 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -136,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int width = source.Width; int height = source.Height; - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( source.Bounds(), configuration, rows => @@ -166,7 +164,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int height = source.Height; Rectangle destinationBounds = destination.Bounds(); - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( source.Bounds(), configuration, rows => @@ -201,7 +199,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int height = source.Height; Rectangle destinationBounds = destination.Bounds(); - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( source.Bounds(), configuration, rows => diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index bacaaa709..3c3b3b5ec 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -26,6 +26,7 @@ + diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index fc93fc04e..6a0aea0ac 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -1,8 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +#if Windows_NT +using System.Security.Principal; +#endif using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Diagnostics.Windows; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; @@ -13,6 +17,14 @@ namespace SixLabors.ImageSharp.Benchmarks public Config() { this.Add(MemoryDiagnoser.Default); + +#if Windows_NT + if (this.IsElevated) + { + this.Add(new NativeMemoryProfiler()); + } +#endif + } public class ShortClr : Config @@ -25,5 +37,15 @@ namespace SixLabors.ImageSharp.Benchmarks Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); } } + +#if Windows_NT + private bool IsElevated + { + get + { + return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); + } + } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 60b1fde8e..3cf0a7da3 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -19,6 +19,7 @@ + diff --git a/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs index 3cfce6b8e..fbe259d2b 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using Xunit; namespace SixLabors.ImageSharp.Tests.Helpers diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs similarity index 94% rename from tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs rename to tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index f9db1a429..0abc9aa13 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Numerics; using System.Threading; -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -16,11 +16,11 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Helpers { - public class ParallelHelperTests + public class ParallelRowIteratorTests { private readonly ITestOutputHelper output; - public ParallelHelperTests(ITestOutputHelper output) + public ParallelRowIteratorTests(ITestOutputHelper output) { this.output = output; } @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers int actualNumberOfSteps = 0; - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( rectangle, parallelSettings, rows => @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); var actualData = new int[maxY]; - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( rectangle, parallelSettings, rows => @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var bufferHashes = new ConcurrentBag(); int actualNumberOfSteps = 0; - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( rectangle, parallelSettings, (RowInterval rows, Memory buffer) => @@ -179,7 +179,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); var actualData = new int[maxY]; - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( rectangle, parallelSettings, (RowInterval rows, Memory buffer) => @@ -225,7 +225,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers int actualNumberOfSteps = 0; - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( rectangle, parallelSettings, rows => @@ -262,7 +262,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rectangle = new Rectangle(0, 0, width, height); int actualNumberOfSteps = 0; - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( rectangle, parallelSettings, (RowInterval rows, Memory buffer) => @@ -325,7 +325,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers // Fill actual data using IterateRows: var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); - ParallelHelper.IterateRows( + ParallelRowIterator.IterateRows( rect, settings, rows => @@ -354,7 +354,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rect = new Rectangle(0, 0, width, height); ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelHelper.IterateRows(rect, parallelSettings, rows => { })); + () => ParallelRowIterator.IterateRows(rect, parallelSettings, rows => { })); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } @@ -371,7 +371,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rect = new Rectangle(0, 0, width, height); ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelHelper.IterateRowsWithTempBuffer(rect, parallelSettings, (rows, memory) => { })); + () => ParallelRowIterator.IterateRowsWithTempBuffer(rect, parallelSettings, (rows, memory) => { })); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 70d39024e..4c9318f93 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -7,7 +7,6 @@ using System.IO; using System.Numerics; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -703,7 +702,7 @@ namespace SixLabors.ImageSharp.Tests { Rectangle sourceRectangle = this.SourceRectangle; Configuration configuration = this.Configuration; - ParallelHelper.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRowsWithTempBuffer( sourceRectangle, configuration, (rows, temp) => From 7f19fb21cb5e65af02a8329710dc713c7c23b8d2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Feb 2020 21:18:07 +1100 Subject: [PATCH 07/58] Convert AffineTranformProcessor --- .../AffineTransformProcessor{TPixel}.cs | 161 ++++++++++++------ .../Transforms/CropProcessor{TPixel}.cs | 11 +- 2 files changed, 114 insertions(+), 58 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 11f8719ef..32aecbedb 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -3,7 +3,9 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -48,7 +50,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } int width = this.targetSize.Width; - Rectangle sourceBounds = this.SourceRectangle; var targetBounds = new Rectangle(Point.Empty, this.targetSize); Configuration configuration = this.Configuration; @@ -57,70 +58,124 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { + Rectangle sourceBounds = this.SourceRectangle; + var nearestRowAction = new NearestNeighborRowIntervalAction(ref sourceBounds, ref matrix, width, source, destination); + ParallelRowIterator.IterateRows( targetBounds, configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destRow = destination.GetPixelRowSpan(y); - - for (int x = 0; x < width; x++) - { - var point = Point.Transform(new Point(x, y), matrix); - if (sourceBounds.Contains(point.X, point.Y)) - { - destRow[x] = source[point.X, point.Y]; - } - } - } - }); + in nearestRowAction); return; } - var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); + using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); + var rowAction = new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination); + + ParallelRowIterator.IterateRows( + targetBounds, + configuration, + in rowAction); + } + + private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction + { + private readonly Rectangle bounds; + private readonly Matrix3x2 matrix; + private readonly int maxX; + private readonly ImageFrame source; + private readonly ImageFrame destination; - try + [MethodImpl(InliningOptions.ShortMethod)] + public NearestNeighborRowIntervalAction( + ref Rectangle bounds, + ref Matrix3x2 matrix, + int maxX, + ImageFrame source, + ImageFrame destination) { - ParallelRowIterator.IterateRowsWithTempBuffer( - targetBounds, - configuration, - (rows, vectorBuffer) => + this.bounds = bounds; + this.matrix = matrix; + this.maxX = maxX; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) + { + var point = Point.Transform(new Point(x, y), this.matrix); + if (this.bounds.Contains(point.X, point.Y)) { - Span vectorSpan = vectorBuffer.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan); - ref float ySpanRef = ref kernel.GetYStartReference(y); - ref float xSpanRef = ref kernel.GetXStartReference(y); - - for (int x = 0; x < width; x++) - { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - var point = Vector2.Transform(new Vector2(x, y), matrix); - kernel.Convolve( - point, - x, - ref ySpanRef, - ref xSpanRef, - source.PixelBuffer, - vectorSpan); - } - - PixelOperations.Instance.FromVector4Destructive( - configuration, - vectorSpan, - targetRowSpan); - } - }); + destRow[x] = this.source[point.X, point.Y]; + } + } + } + } + } + + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly Configuration configuration; + private readonly TransformKernelMap kernelMap; + private readonly Matrix3x2 matrix; + private readonly int maxX; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + Configuration configuration, + TransformKernelMap kernelMap, + ref Matrix3x2 matrix, + int maxX, + ImageFrame source, + ImageFrame destination) + { + this.configuration = configuration; + this.kernelMap = kernelMap; + this.matrix = matrix; + this.maxX = maxX; + this.source = source; + this.destination = destination; } - finally + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) { - kernel.Dispose(); + Span vectorSpan = memory.Span; + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = this.destination.GetPixelRowSpan(y); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, vectorSpan); + ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); + ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); + + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var point = Vector2.Transform(new Vector2(x, y), this.matrix); + this.kernelMap.Convolve( + point, + x, + ref ySpanRef, + ref xSpanRef, + this.source.PixelBuffer, + vectorSpan); + } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + vectorSpan, + targetRowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 6baea69ed..8afe2d7da 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -48,10 +48,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle bounds = this.cropRectangle; // Copying is cheap, we should process more pixels per task: - ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration) - .MultiplyMinimumPixelsPerTask(4); + ParallelExecutionSettings parallelSettings = + ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - var rowAction = new RowAction(ref bounds, source, destination); + var rowAction = new RowIntervalAction(ref bounds, source, destination); ParallelRowIterator.IterateRows( bounds, @@ -59,13 +59,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms in rowAction); } - private readonly struct RowAction : IRowIntervalAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly ImageFrame source; private readonly ImageFrame destination; - public RowAction(ref Rectangle bounds, ImageFrame source, ImageFrame destination) + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction(ref Rectangle bounds, ImageFrame source, ImageFrame destination) { this.bounds = bounds; this.source = source; From 01f2c05f72503ade92e8eb79a992358b6b5f2dcf Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Feb 2020 21:53:52 +1100 Subject: [PATCH 08/58] Update ProjectiveTransformProcessor{TPixel}.cs --- .../ProjectiveTransformProcessor{TPixel}.cs | 167 ++++++++++++------ 1 file changed, 111 insertions(+), 56 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index b63e7eff3..76bbc3a90 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -3,7 +3,9 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -48,7 +50,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } int width = this.targetSize.Width; - Rectangle sourceBounds = this.SourceRectangle; var targetBounds = new Rectangle(Point.Empty, this.targetSize); Configuration configuration = this.Configuration; @@ -57,73 +58,127 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { + Rectangle sourceBounds = this.SourceRectangle; + var nearestRowAction = new NearestNeighborRowIntervalAction(ref sourceBounds, ref matrix, width, source, destination); + ParallelRowIterator.IterateRows( targetBounds, configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destRow = destination.GetPixelRowSpan(y); - - for (int x = 0; x < width; x++) - { - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); - int px = (int)MathF.Round(point.X); - int py = (int)MathF.Round(point.Y); - - if (sourceBounds.Contains(px, py)) - { - destRow[x] = source[px, py]; - } - } - } - }); + in nearestRowAction); return; } - var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); + using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); + var rowAction = new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination); + + ParallelRowIterator.IterateRows( + targetBounds, + configuration, + in rowAction); + } + + private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction + { + private readonly Rectangle bounds; + private readonly Matrix4x4 matrix; + private readonly int maxX; + private readonly ImageFrame source; + private readonly ImageFrame destination; - try + [MethodImpl(InliningOptions.ShortMethod)] + public NearestNeighborRowIntervalAction( + ref Rectangle bounds, + ref Matrix4x4 matrix, + int maxX, + ImageFrame source, + ImageFrame destination) { - ParallelRowIterator.IterateRowsWithTempBuffer( - targetBounds, - configuration, - (rows, vectorBuffer) => + this.bounds = bounds; + this.matrix = matrix; + this.maxX = maxX; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) + { + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); + + if (this.bounds.Contains(px, py)) { - Span vectorSpan = vectorBuffer.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan); - ref float ySpanRef = ref kernel.GetYStartReference(y); - ref float xSpanRef = ref kernel.GetXStartReference(y); - - for (int x = 0; x < width; x++) - { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); - kernel.Convolve( - point, - x, - ref ySpanRef, - ref xSpanRef, - source.PixelBuffer, - vectorSpan); - } - - PixelOperations.Instance.FromVector4Destructive( - configuration, - vectorSpan, - targetRowSpan); - } - }); + destRow[x] = this.source[px, py]; + } + } + } } - finally + } + + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly Configuration configuration; + private readonly TransformKernelMap kernelMap; + private readonly Matrix4x4 matrix; + private readonly int maxX; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + Configuration configuration, + TransformKernelMap kernelMap, + ref Matrix4x4 matrix, + int maxX, + ImageFrame source, + ImageFrame destination) { - kernel.Dispose(); + this.configuration = configuration; + this.kernelMap = kernelMap; + this.matrix = matrix; + this.maxX = maxX; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) + { + Span vectorSpan = memory.Span; + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = this.destination.GetPixelRowSpan(y); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, vectorSpan); + ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); + ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); + + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); + this.kernelMap.Convolve( + point, + x, + ref ySpanRef, + ref xSpanRef, + this.source.PixelBuffer, + vectorSpan); + } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + vectorSpan, + targetRowSpan); + } } } } From e40731d5daa5aa5831497a2737debc3170f45f4a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Feb 2020 23:11:36 +1100 Subject: [PATCH 09/58] Internalize, partially optimize and rename Action methods. --- src/ImageSharp/Advanced/IRowIntervalAction.cs | 29 +++++ .../Advanced/IRowIntervalAction{TBuffer}.cs | 40 ++++++- .../Advanced/ParallelRowIterator.cs | 112 ++++++------------ .../Convolution/BokehBlurProcessor{TPixel}.cs | 2 +- .../Convolution2DProcessor{TPixel}.cs | 2 +- .../Convolution2PassProcessor{TPixel}.cs | 2 +- .../ConvolutionProcessor{TPixel}.cs | 2 +- .../PixelRowDelegateProcessorBase{TPixel}.cs | 2 +- .../Filters/FilterProcessor{TPixel}.cs | 2 +- .../Overlays/GlowProcessor{TPixel}.cs | 2 +- .../Overlays/VignetteProcessor{TPixel}.cs | 2 +- .../Helpers/ParallelRowIteratorTests.cs | 8 +- .../TestUtilities/TestImageExtensions.cs | 2 +- 13 files changed, 119 insertions(+), 88 deletions(-) diff --git a/src/ImageSharp/Advanced/IRowIntervalAction.cs b/src/ImageSharp/Advanced/IRowIntervalAction.cs index df422a65f..9a67eea71 100644 --- a/src/ImageSharp/Advanced/IRowIntervalAction.cs +++ b/src/ImageSharp/Advanced/IRowIntervalAction.cs @@ -40,6 +40,35 @@ namespace SixLabors.ImageSharp.Advanced } } + internal readonly struct WrappingRowIntervalAction + { + private readonly WrappingRowIntervalInfo info; + private readonly Action action; + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowIntervalAction(in WrappingRowIntervalInfo info, Action action) + { + this.info = info; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.info.MinY + (i * this.info.StepY); + + if (yMin >= this.info.MaxY) + { + return; + } + + int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + var rows = new RowInterval(yMin, yMax); + + this.action(rows); + } + } + internal readonly struct WrappingRowIntervalAction where T : struct, IRowIntervalAction { diff --git a/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs index 7b841b9cd..1fd088e09 100644 --- a/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs @@ -23,7 +23,43 @@ namespace SixLabors.ImageSharp.Advanced void Invoke(in RowInterval rows, Memory memory); } - internal readonly struct WrappingRowIntervalAction + internal readonly struct WrappingRowIntervalBufferAction + where TBuffer : unmanaged + { + private readonly WrappingRowIntervalInfo info; + private readonly MemoryAllocator allocator; + private readonly Action> action; + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowIntervalBufferAction( + in WrappingRowIntervalInfo info, + MemoryAllocator allocator, + Action> action) + { + this.info = info; + this.allocator = allocator; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.info.MinY + (i * this.info.StepY); + + if (yMin >= this.info.MaxY) + { + return; + } + + int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + var rows = new RowInterval(yMin, yMax); + + using IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX); + this.action(rows, buffer.Memory); + } + } + + internal readonly struct WrappingRowIntervalBufferAction where T : struct, IRowIntervalAction where TBuffer : unmanaged { @@ -32,7 +68,7 @@ namespace SixLabors.ImageSharp.Advanced private readonly T action; [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowIntervalAction( + public WrappingRowIntervalBufferAction( in WrappingRowIntervalInfo info, MemoryAllocator allocator, in T action) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 3bc9e1f90..5c1b4110e 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); - var rowAction = new WrappingRowIntervalAction(in rowInfo, allocator, in body); + var rowAction = new WrappingRowIntervalBufferAction(in rowInfo, allocator, in body); Parallel.For( 0, @@ -145,11 +145,9 @@ namespace SixLabors.ImageSharp.Advanced /// The . /// The to get the parallel settings from. /// The method body defining the iteration logic on a single . - // [Obsolete("Use non-allocating generic versions instead.")] - public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) + internal static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, body); } @@ -159,80 +157,81 @@ namespace SixLabors.ImageSharp.Advanced /// The . /// The . /// The method body defining the iteration logic on a single . - // [Obsolete("Use non-allocating generic versions instead.")] - public static void IterateRows( + internal static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, Action body) { ValidateRectangle(rectangle); - int maxSteps = DivideCeil( - rectangle.Width * rectangle.Height, - parallelSettings.MinimumPixelsProcessedPerTask); + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); // Avoid TPL overhead in this trivial case: if (numOfSteps == 1) { - var rows = new RowInterval(rectangle.Top, rectangle.Bottom); + var rows = new RowInterval(top, bottom); body(rows); return; } int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - - int top = rectangle.Top; - int bottom = rectangle.Bottom; + var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); + var rowAction = new WrappingRowIntervalAction(in rowInfo, body); Parallel.For( 0, numOfSteps, parallelOptions, - i => - { - int yMin = top + (i * verticalStep); - - if (yMin >= bottom) - { - return; - } - - int yMax = Math.Min(yMin + verticalStep, bottom); - - var rows = new RowInterval(yMin, yMax); + rowAction.Invoke); + } - body(rows); - }); + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + internal static void IterateRows( + Rectangle rectangle, + Configuration configuration, + Action> body) + where TBuffer : unmanaged + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows(rectangle, in parallelSettings, body); } /// /// Iterate through the rows of a rectangle in optimized batches defined by -s /// instantiating a temporary buffer for each invocation. /// - // [Obsolete("Use non-allocating generic versions instead.")] - internal static void IterateRowsWithTempBuffer( + internal static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, - Action> body) - where T : unmanaged + Action> body) + where TBuffer : unmanaged { ValidateRectangle(rectangle); - int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - - MemoryAllocator memoryAllocator = parallelSettings.MemoryAllocator; + MemoryAllocator allocator = parallelSettings.MemoryAllocator; // Avoid TPL overhead in this trivial case: if (numOfSteps == 1) { - var rows = new RowInterval(rectangle.Top, rectangle.Bottom); - using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width)) + var rows = new RowInterval(top, bottom); + using (IMemoryOwner buffer = allocator.Allocate(width)) { body(rows, buffer.Memory); } @@ -241,48 +240,15 @@ namespace SixLabors.ImageSharp.Advanced } int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - - int top = rectangle.Top; - int bottom = rectangle.Bottom; + var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); + var rowAction = new WrappingRowIntervalBufferAction(in rowInfo, allocator, body); Parallel.For( 0, numOfSteps, parallelOptions, - i => - { - int yMin = top + (i * verticalStep); - - if (yMin >= bottom) - { - return; - } - - int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); - - var rows = new RowInterval(yMin, yMax); - - using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width)) - { - body(rows, buffer.Memory); - } - }); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - // [Obsolete("Use non-allocating generic versions instead.")] - internal static void IterateRowsWithTempBuffer( - Rectangle rectangle, - Configuration configuration, - Action> body) - where T : unmanaged - { - IterateRowsWithTempBuffer(rectangle, ParallelExecutionSettings.FromConfiguration(configuration), body); + rowAction.Invoke); } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index bb89f0318..afce39dcd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -427,7 +427,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int width = workingRectangle.Width; float exp = this.gamma; - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( workingRectangle, configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 431e1c604..e2088084a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); int width = workingRectangle.Width; - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( workingRectangle, this.Configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index ce13b4074..3f12b2a28 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); int width = workingRectangle.Width; - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( workingRectangle, configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index faffbd575..33b80688c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); int width = workingRectangle.Width; - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( workingRectangle, this.Configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs index bca288783..8926ddc66 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects Configuration configuration = this.Configuration; PixelConversionModifiers modifiers = this.modifiers; - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( interest, this.Configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index 64d705a2f..a8ce67af3 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters ColorMatrix matrix = this.definition.Matrix; - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( interest, this.Configuration, (rows, vectorBuffer) => diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 0032a7983..0271caa5d 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays { rowColors.GetSpan().Fill(glowColor); - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( workingRect, configuration, (rows, amounts) => diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index 95fe35b09..55cacccdf 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays { rowColors.GetSpan().Fill(vignetteColor); - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( workingRect, configuration, (rows, amounts) => diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 0abc9aa13..243ffe220 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var bufferHashes = new ConcurrentBag(); int actualNumberOfSteps = 0; - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( rectangle, parallelSettings, (RowInterval rows, Memory buffer) => @@ -179,7 +179,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); var actualData = new int[maxY]; - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( rectangle, parallelSettings, (RowInterval rows, Memory buffer) => @@ -262,7 +262,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rectangle = new Rectangle(0, 0, width, height); int actualNumberOfSteps = 0; - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( rectangle, parallelSettings, (RowInterval rows, Memory buffer) => @@ -371,7 +371,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rect = new Rectangle(0, 0, width, height); ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRowsWithTempBuffer(rect, parallelSettings, (rows, memory) => { })); + () => ParallelRowIterator.IterateRows(rect, parallelSettings, (rows, memory) => { })); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 4c9318f93..9aaca3e3f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -702,7 +702,7 @@ namespace SixLabors.ImageSharp.Tests { Rectangle sourceRectangle = this.SourceRectangle; Configuration configuration = this.Configuration; - ParallelRowIterator.IterateRowsWithTempBuffer( + ParallelRowIterator.IterateRows( sourceRectangle, configuration, (rows, temp) => From 3b3e6ab50790ba77d518ca7043af196bb13d0df1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Feb 2020 23:47:06 +1100 Subject: [PATCH 10/58] Fix non-windows build --- tests/ImageSharp.Benchmarks/Config.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 6a0aea0ac..fd0b213b3 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -3,10 +3,10 @@ #if Windows_NT using System.Security.Principal; +using BenchmarkDotNet.Diagnostics.Windows; #endif using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Diagnostics.Windows; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; From 079e2a671b198304c9997bdca195679c4e987a1b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 6 Feb 2020 17:00:31 +0100 Subject: [PATCH 11/58] Add XML comments to the CropProcessor --- .../Processors/Transforms/CropProcessor{TPixel}.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 8afe2d7da..103c5d3ff 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class CropProcessor : TransformProcessor where TPixel : struct, IPixel { - private Rectangle cropRectangle; + private readonly Rectangle cropRectangle; /// /// Initializes a new instance of the class. @@ -59,12 +59,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms in rowAction); } + /// + /// A implementing the processor logic for . + /// private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly ImageFrame source; private readonly ImageFrame destination; + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The source for the current instance. + /// The destination for the current instance. [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalAction(ref Rectangle bounds, ImageFrame source, ImageFrame destination) { @@ -73,6 +82,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.destination = destination; } + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { From 2f83149207691f60bcbdabdea4a6cb253b63edea Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 6 Feb 2020 17:00:41 +0100 Subject: [PATCH 12/58] Refactor BokehBlurProcessor --- .../Convolution/BokehBlurProcessor{TPixel}.cs | 408 ++++++++++-------- 1 file changed, 226 insertions(+), 182 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index afce39dcd..def189753 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -268,17 +268,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution protected override void OnFrameApply(ImageFrame source) { // Preliminary gamma highlight pass - this.ApplyGammaExposure(source.PixelBuffer, this.SourceRectangle, this.Configuration); + ParallelRowIterator.IterateRows( + this.SourceRectangle, + this.Configuration, + new ApplyGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); // Create a 0-filled buffer to use to store the result of the component convolutions - using (Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean)) - { - // Perform the 1D convolutions on all the kernel components and accumulate the results - this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processingBuffer); + using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean); - // Apply the inverse gamma exposure pass, and write the final pixel data - this.ApplyInverseGammaExposure(source.PixelBuffer, processingBuffer, this.SourceRectangle, this.Configuration); - } + // Perform the 1D convolutions on all the kernel components and accumulate the results + this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processingBuffer); + + float inverseGamma = 1 / this.gamma; + + // Apply the inverse gamma exposure pass, and write the final pixel data + ParallelRowIterator.IterateRows( + this.SourceRectangle, + this.Configuration, + new ApplyInverseGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); } /// @@ -294,216 +301,253 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Configuration configuration, Buffer2D processingBuffer) { - using (Buffer2D firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) + // Allocate the buffer with the intermediate convolution results + using Buffer2D firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + // Perform two 1D convolutions for each component in the current instance + ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); + ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); + for (int i = 0; i < this.kernels.Length; i++) { - // Perform two 1D convolutions for each component in the current instance - ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); - ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); - for (int i = 0; i < this.kernels.Length; i++) - { - // Compute the resulting complex buffer for the current component - var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - Complex64[] kernel = Unsafe.Add(ref baseRef, i); - Vector4 parameters = Unsafe.Add(ref paramsRef, i); - - // Compute the two 1D convolutions and accumulate the partial results on the target buffer - this.ApplyConvolution(firstPassBuffer, source.PixelBuffer, interest, kernel, configuration); - this.ApplyConvolution(processingBuffer, firstPassBuffer, interest, kernel, configuration, parameters.Z, parameters.W); - } + // Compute the resulting complex buffer for the current component + Complex64[] kernel = Unsafe.Add(ref baseRef, i); + Vector4 parameters = Unsafe.Add(ref paramsRef, i); + + // Compute the vertical 1D convolution + ParallelRowIterator.IterateRows( + sourceRectangle, + configuration, + new ApplyVerticalConvolutionRowIntervalAction(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); + + // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer + ParallelRowIterator.IterateRows( + sourceRectangle, + configuration, + new ApplyHorizontalConvolutionRowIntervalAction(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); } } /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. + /// A implementing the vertical convolution logic for . /// - /// The target values to use to store the results. - /// The source pixels. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The 1D kernel. - /// The - private void ApplyConvolution( - Buffer2D targetValues, - Buffer2D sourcePixels, - Rectangle sourceRectangle, - Complex64[] kernel, - Configuration configuration) + private readonly struct ApplyVerticalConvolutionRowIntervalAction : IRowIntervalAction { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourcePixels; + private readonly Complex64[] kernel; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The target values to use to store the results. + /// The source pixels. Cannot be null. + /// The 1D kernel. + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyVerticalConvolutionRowIntervalAction( + ref Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourcePixels, + Complex64[] kernel) + { + this.bounds = bounds; + this.targetValues = targetValues; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + } - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; - ParallelRowIterator.IterateRows( - workingRectangle, - configuration, - rows => + for (int y = rows.Min; y < rows.Max; y++) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); - for (int x = 0; x < width; x++) - { - Buffer2DUtils.Convolve4(kernel, sourcePixels, targetRowSpan, y, x, startY, maxY, startX, maxX); - } + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, maxY, this.bounds.X, maxX); } - }); + } + } } /// - /// Applies the process to the specified portion of the specified buffer at the specified location - /// and with the specified size. + /// A implementing the horizontal convolution logic for . /// - /// The target values to use to store the results. - /// The source complex values. Cannot be null. - /// The structure that specifies the portion of the image object to draw. - /// The 1D kernel. - /// The - /// The weight factor for the real component of the complex pixel values. - /// The weight factor for the imaginary component of the complex pixel values. - private void ApplyConvolution( - Buffer2D targetValues, - Buffer2D sourceValues, - Rectangle sourceRectangle, - Complex64[] kernel, - Configuration configuration, - float z, - float w) + private readonly struct ApplyHorizontalConvolutionRowIntervalAction : IRowIntervalAction { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourceValues; + private readonly Complex64[] kernel; + private readonly float z; + private readonly float w; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The target values to use to store the results. + /// The source complex values. Cannot be null. + /// The 1D kernel. + /// The weight factor for the real component of the complex pixel values. + /// The weight factor for the imaginary component of the complex pixel values. + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyHorizontalConvolutionRowIntervalAction( + ref Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourceValues, + Complex64[] kernel, + float z, + float w) + { + this.bounds = bounds; + this.targetValues = targetValues; + this.sourceValues = sourceValues; + this.kernel = kernel; + this.z = z; + this.w = w; + } - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; - ParallelRowIterator.IterateRows( - workingRectangle, - configuration, - rows => + for (int y = rows.Min; y < rows.Max; y++) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); - for (int x = 0; x < width; x++) - { - Buffer2DUtils.Convolve4AndAccumulatePartials(kernel, sourceValues, targetRowSpan, y, x, startY, maxY, startX, maxX, z, w); - } + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, maxY, this.bounds.X, maxX, this.z, this.w); } - }); + } + } } /// - /// Applies the gamma correction/highlight to the input pixel buffer. + /// A implementing the convolution logic for . /// - /// The target pixel buffer to adjust. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - private void ApplyGammaExposure( - Buffer2D targetPixels, - Rectangle sourceRectangle, - Configuration configuration) + private readonly struct ApplyGammaExposureRowIntervalAction : IRowIntervalAction { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - float exp = this.gamma; - - ParallelRowIterator.IterateRows( - workingRectangle, - configuration, - (rows, vectorBuffer) => + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Configuration configuration; + private readonly float gamma; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The target pixel buffer to adjust. + /// The + /// The gamma parameter to use. + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyGammaExposureRowIntervalAction( + Rectangle bounds, + Buffer2D targetPixels, + Configuration configuration, + float gamma) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.configuration = configuration; + this.gamma = gamma; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) + { + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan); + + for (int x = 0; x < this.bounds.Width; x++) { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan); - - for (int x = 0; x < width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, x); - v.X = MathF.Pow(v.X, exp); - v.Y = MathF.Pow(v.Y, exp); - v.Z = MathF.Pow(v.Z, exp); - } - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan); - } - }); + ref Vector4 v = ref Unsafe.Add(ref baseRef, x); + v.X = MathF.Pow(v.X, this.gamma); + v.Y = MathF.Pow(v.Y, this.gamma); + v.Z = MathF.Pow(v.Z, this.gamma); + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan.Slice(0, length), targetRowSpan); + } + } } /// - /// Applies the inverse gamma correction/highlight pass, and converts the input buffer into pixel values. + /// A implementing the convolution logic for . /// - /// The target pixels to apply the process to. - /// The source values. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - private void ApplyInverseGammaExposure( - Buffer2D targetPixels, - Buffer2D sourceValues, - Rectangle sourceRectangle, - Configuration configuration) + private readonly struct ApplyInverseGammaExposureRowIntervalAction : IRowIntervalAction { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourceValues; + private readonly Configuration configuration; + private readonly float inverseGamma; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The target pixels to apply the process to. + /// The source values. Cannot be null. + /// The + /// The inverse gamma parameter to use. + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyInverseGammaExposureRowIntervalAction( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourceValues, + Configuration configuration, + float inverseGamma) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourceValues = sourceValues; + this.configuration = configuration; + this.inverseGamma = inverseGamma; + } - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - float expGamma = 1 / this.gamma; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + Vector4 low = Vector4.Zero; + var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - ParallelRowIterator.IterateRows( - workingRectangle, - configuration, - rows => + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); + + for (int x = 0; x < this.bounds.Width; x++) { - Vector4 low = Vector4.Zero; - var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetPixelSpan = targetPixels.GetRowSpan(y).Slice(startX); - Span sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX); - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - - for (int x = 0; x < width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); - var clamp = Vector4.Clamp(v, low, high); - v.X = MathF.Pow(clamp.X, expGamma); - v.Y = MathF.Pow(clamp.Y, expGamma); - v.Z = MathF.Pow(clamp.Z, expGamma); - } - - PixelOperations.Instance.FromVector4Destructive(configuration, sourceRowSpan.Slice(0, width), targetPixelSpan, PixelConversionModifiers.Premultiply); - } - }); + ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); + var clamp = Vector4.Clamp(v, low, high); + v.X = MathF.Pow(clamp.X, this.inverseGamma); + v.Y = MathF.Pow(clamp.Y, this.inverseGamma); + v.Z = MathF.Pow(clamp.Z, this.inverseGamma); + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); + } + } } } } From 6298c78b8505a6b96ccaa76b0d491c1ba1f8625c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 6 Feb 2020 19:39:29 +0100 Subject: [PATCH 13/58] Refactor Convolution2PassProcessor --- .../Convolution/BokehBlurProcessor{TPixel}.cs | 4 +- .../Convolution2PassProcessor{TPixel}.cs | 181 ++++++++++-------- 2 files changed, 102 insertions(+), 83 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index def189753..215e0d729 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -434,7 +434,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - /// A implementing the convolution logic for . + /// A implementing the gamma exposure logic for . /// private readonly struct ApplyGammaExposureRowIntervalAction : IRowIntervalAction { @@ -490,7 +490,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - /// A implementing the convolution logic for . + /// A implementing the inverse gamma exposure logic for . /// private readonly struct ApplyInverseGammaExposureRowIntervalAction : IRowIntervalAction { diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 3f12b2a28..65a0ace36 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -58,95 +59,113 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) - { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, this.Configuration); - this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, this.Configuration); - } + using Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + // Horizontal convolution + ParallelRowIterator.IterateRows( + interest, + this.Configuration, + new RowIntervalAction(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); + + // Vertical convolution + ParallelRowIterator.IterateRows( + interest, + this.Configuration, + new RowIntervalAction(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); } /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. + /// A implementing the convolution logic for . /// - /// The target pixels to apply the process to. - /// The source pixels. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The kernel operator. - /// The - private void ApplyConvolution( - Buffer2D targetPixels, - Buffer2D sourcePixels, - Rectangle sourceRectangle, - in DenseMatrix kernel, - Configuration configuration) + private readonly struct RowIntervalAction : IRowIntervalAction { - DenseMatrix matrix = kernel; - bool preserveAlpha = this.PreserveAlpha; - - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - - ParallelRowIterator.IterateRows( - workingRectangle, - configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; - for (int y = rows.Min; y < rows.Max; y++) + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The target pixel buffer to adjust. + /// The source pixels. Cannot be null. + /// The kernel operator. + /// The + /// Whether the convolution filter is applied to alpha as well as the color channels. + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernel, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) + { + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); - - if (preserveAlpha) - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve3( - in matrix, - sourcePixels, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - else - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve4( - in matrix, - sourcePixels, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); } - }); + } + else + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); + } + } } } } From c5ed8697fe102ad348a6c5b774acb712f39b9e8e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 6 Feb 2020 19:47:36 +0100 Subject: [PATCH 14/58] Refactor ConvolutionProcessor --- .../ConvolutionProcessor{TPixel}.cs | 162 +++++++++++------- 1 file changed, 98 insertions(+), 64 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 33b80688c..95e5107c7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -50,76 +51,109 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - DenseMatrix matrix = this.KernelXY; - bool preserveAlpha = this.PreserveAlpha; + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - int maxY = endY - 1; - int maxX = endX - 1; - - using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) + + ParallelRowIterator.IterateRows( + interest, + this.Configuration, + new RowIntervalAction(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha)); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The target pixel buffer to adjust. + /// The source pixels. Cannot be null. + /// The kernel operator. + /// The + /// Whether the convolution filter is applied to alpha as well as the color channels. + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernel, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) { - source.CopyTo(targetPixels); + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; - ParallelRowIterator.IterateRows( - workingRectangle, - this.Configuration, - (rows, vectorBuffer) => + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan); - - if (preserveAlpha) - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve3( - in matrix, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - else - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve4( - in matrix, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - - PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan); - } - }); - - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); + } } } } From 58418b43cc8e720b79621a1db252c35dd07a8119 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 6 Feb 2020 20:13:43 +0100 Subject: [PATCH 15/58] Refactor EdgeDetectorCompassProcessor --- .../EdgeDetectorCompassProcessor{TPixel}.cs | 155 +++++++++++------- 1 file changed, 95 insertions(+), 60 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 185142758..e85205c41 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -65,77 +65,112 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); - // we need a clean copy for each pass to start from - using (ImageFrame cleanCopy = source.Clone()) + // We need a clean copy for each pass to start from + using ImageFrame cleanCopy = source.Clone(); + + using (var processor = new ConvolutionProcessor(this.Configuration, kernels[0], true, this.Source, this.SourceRectangle)) { - using (var processor = new ConvolutionProcessor(this.Configuration, kernels[0], true, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + processor.Apply(source); + } - if (kernels.Length == 1) - { - return; - } + if (kernels.Length == 1) + { + return; + } - int shiftY = startY; - int shiftX = startX; + int shiftY = startY; + int shiftX = startX; - // Reset offset if necessary. - if (minX > 0) - { - shiftX = 0; - } + // Reset offset if necessary + if (minX > 0) + { + shiftX = 0; + } - if (minY > 0) + if (minY > 0) + { + shiftY = 0; + } + + // Additional runs + for (int i = 1; i < kernels.Length; i++) + { + using ImageFrame pass = cleanCopy.Clone(); + + using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, this.SourceRectangle)) { - shiftY = 0; + processor.Apply(pass); } - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + ParallelRowIterator.IterateRows( + Rectangle.FromLTRB(minX, minY, maxX, maxY), + this.Configuration, + new RowIntervalAction(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX)); + } + } - // Additional runs. - // ReSharper disable once ForCanBeConvertedToForeach - for (int i = 1; i < kernels.Length; i++) + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly Buffer2D targetPixels; + private readonly Buffer2D passPixels; + private readonly int minX; + private readonly int maxX; + private readonly int shiftY; + private readonly int shiftX; + + /// + /// Initializes a new instance of the struct. + /// + /// The target pixel buffer to adjust. + /// The processed pixels for the current iteration. Cannot be null. + /// The minimum horizontal offset. + /// The maximum horizontal offset. + /// The vertical offset shift. + /// The horizontal offset shift. + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + Buffer2D targetPixels, + Buffer2D passPixels, + int minX, + int maxX, + int shiftY, + int shiftX) + { + this.targetPixels = targetPixels; + this.passPixels = passPixels; + this.minX = minX; + this.maxX = maxX; + this.shiftY = shiftY; + this.shiftX = shiftX; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) { - using (ImageFrame pass = cleanCopy.Clone()) + int offsetY = y - this.shiftY; + + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(offsetY)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(offsetY)); + + for (int x = this.minX; x < this.maxX; x++) { - using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, this.SourceRectangle)) - { - processor.Apply(pass); - } - - Buffer2D passPixels = pass.PixelBuffer; - Buffer2D targetPixels = source.PixelBuffer; - - ParallelRowIterator.IterateRows( - workingRect, - this.Configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - shiftY; - - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY)); - - for (int x = minX; x < maxX; x++) - { - int offsetX = x - shiftX; - - // Grab the max components of the two pixels - ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); - ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); - - var pixelValue = Vector4.Max( - currentPassPixel.ToVector4(), - currentTargetPixel.ToVector4()); - - currentTargetPixel.FromVector4(pixelValue); - } - } - }); + int offsetX = x - this.shiftX; + + // Grab the max components of the two pixels + ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); + ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); + + var pixelValue = Vector4.Max( + currentPassPixel.ToVector4(), + currentTargetPixel.ToVector4()); + + currentTargetPixel.FromVector4(pixelValue); } } } From 1564149ece0425fffe71ca860f414331abffdd1b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 6 Feb 2020 20:22:18 +0100 Subject: [PATCH 16/58] Refactor Convolution2DProcessor --- .../Convolution/BoxBlurProcessor{TPixel}.cs | 7 +- .../Convolution2DProcessor{TPixel}.cs | 171 +++++++++++------- .../EdgeDetector2DProcessor{TPixel}.cs | 7 +- 3 files changed, 110 insertions(+), 75 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs index 095c91bac..50004655f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs @@ -40,10 +40,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index e2088084a..cc48ec474 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -59,79 +60,115 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - DenseMatrix matrixY = this.KernelY; - DenseMatrix matrixX = this.KernelX; - bool preserveAlpha = this.PreserveAlpha; + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); + + source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - int maxY = endY - 1; - int maxX = endX - 1; - - using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) + + ParallelRowIterator.IterateRows( + interest, + this.Configuration, + new RowIntervalAction(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha)); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernelY; + private readonly DenseMatrix kernelX; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The target pixel buffer to adjust. + /// The source pixels. Cannot be null. + /// The vertical kernel operator. + /// The horizontal kernel operator. + /// The + /// Whether the convolution filter is applied to alpha as well as the color channels. + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernelY, + DenseMatrix kernelX, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernelY = kernelY; + this.kernelX = kernelX; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) { - source.CopyTo(targetPixels); + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; - ParallelRowIterator.IterateRows( - workingRectangle, - this.Configuration, - (rows, vectorBuffer) => + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve2D3( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan); - - if (preserveAlpha) - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve2D3( - in matrixY, - in matrixX, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - else - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve2D4( - in matrixY, - in matrixX, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - - PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan); - } - }); - - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + DenseMatrixUtils.Convolve2D4( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs index 31c4fad79..c8c57fc29 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs @@ -63,10 +63,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2DProcessor(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2DProcessor(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle); + + processor.Apply(source); } } } From d844f8ac8821c6633661a9ba4dafd3f767e7d365 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 6 Feb 2020 23:10:28 +0100 Subject: [PATCH 17/58] Add readonly modifiers to APIs in Rectangle --- src/ImageSharp/Primitives/Rectangle.cs | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs index 95b01fd9d..5b2e9411c 100644 --- a/src/ImageSharp/Primitives/Rectangle.cs +++ b/src/ImageSharp/Primitives/Rectangle.cs @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp public Point Location { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new Point(this.X, this.Y); + readonly get => new Point(this.X, this.Y); [MethodImpl(MethodImplOptions.AggressiveInlining)] set @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp public Size Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new Size(this.Width, this.Height); + readonly get => new Size(this.Width, this.Height); [MethodImpl(MethodImplOptions.AggressiveInlining)] set @@ -112,17 +112,17 @@ namespace SixLabors.ImageSharp /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); + public readonly bool IsEmpty => this.Equals(Empty); /// /// Gets the y-coordinate of the top edge of this . /// - public int Top => this.Y; + public readonly int Top => this.Y; /// /// Gets the x-coordinate of the right edge of this . /// - public int Right + public readonly int Right { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => unchecked(this.X + this.Width); @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp /// /// Gets the y-coordinate of the bottom edge of this . /// - public int Bottom + public readonly int Bottom { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => unchecked(this.Y + this.Height); @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp /// /// Gets the x-coordinate of the left edge of this . /// - public int Left => this.X; + public readonly int Left => this.X; /// /// Creates a with the coordinates of the specified . @@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp /// The out value for Y. /// The out value for the width. /// The out value for the height. - public void Deconstruct(out int x, out int y, out int width, out int height) + public readonly void Deconstruct(out int x, out int y, out int width, out int height) { x = this.X; y = this.Y; @@ -383,7 +383,7 @@ namespace SixLabors.ImageSharp /// The y-coordinate of the given point. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; + public readonly bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; /// /// Determines if the specified point is contained within the rectangular region defined by this . @@ -391,7 +391,7 @@ namespace SixLabors.ImageSharp /// The point. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(Point point) => this.Contains(point.X, point.Y); + public readonly bool Contains(Point point) => this.Contains(point.X, point.Y); /// /// Determines if the rectangular region represented by is entirely contained @@ -400,7 +400,7 @@ namespace SixLabors.ImageSharp /// The rectangle. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(Rectangle rectangle) => + public readonly bool Contains(Rectangle rectangle) => (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); @@ -411,7 +411,7 @@ namespace SixLabors.ImageSharp /// The other Rectange. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IntersectsWith(Rectangle rectangle) => + public readonly bool IntersectsWith(Rectangle rectangle) => (rectangle.X < this.Right) && (this.X < rectangle.Right) && (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); @@ -438,13 +438,13 @@ namespace SixLabors.ImageSharp } /// - public override int GetHashCode() + public override readonly int GetHashCode() { return HashCode.Combine(this.X, this.Y, this.Width, this.Height); } /// - public override string ToString() + public override readonly string ToString() { return $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; } @@ -454,10 +454,10 @@ namespace SixLabors.ImageSharp /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rectangle other) => + public readonly bool Equals(Rectangle other) => this.X.Equals(other.X) && this.Y.Equals(other.Y) && this.Width.Equals(other.Width) && this.Height.Equals(other.Height); } -} \ No newline at end of file +} From 2bc5d82fbb569bb2f3f88a13b38b696ecaa329e7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 6 Feb 2020 23:10:49 +0100 Subject: [PATCH 18/58] Removed unnecessary XML comments --- .../Convolution/BokehBlurProcessor{TPixel}.cs | 31 ------------------- .../Convolution2DProcessor{TPixel}.cs | 10 ------ .../Convolution2PassProcessor{TPixel}.cs | 9 ------ .../ConvolutionProcessor{TPixel}.cs | 9 ------ .../EdgeDetectorCompassProcessor{TPixel}.cs | 9 ------ .../AffineTransformProcessor{TPixel}.cs | 23 ++++++++------ 6 files changed, 14 insertions(+), 77 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 215e0d729..d2c547bac 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -337,13 +337,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly Buffer2D sourcePixels; private readonly Complex64[] kernel; - /// - /// Initializes a new instance of the struct. - /// - /// The target processing bounds for the current instance. - /// The target values to use to store the results. - /// The source pixels. Cannot be null. - /// The 1D kernel. [MethodImpl(InliningOptions.ShortMethod)] public ApplyVerticalConvolutionRowIntervalAction( ref Rectangle bounds, @@ -388,15 +381,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly float z; private readonly float w; - /// - /// Initializes a new instance of the struct. - /// - /// The target processing bounds for the current instance. - /// The target values to use to store the results. - /// The source complex values. Cannot be null. - /// The 1D kernel. - /// The weight factor for the real component of the complex pixel values. - /// The weight factor for the imaginary component of the complex pixel values. [MethodImpl(InliningOptions.ShortMethod)] public ApplyHorizontalConvolutionRowIntervalAction( ref Rectangle bounds, @@ -443,13 +427,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly Configuration configuration; private readonly float gamma; - /// - /// Initializes a new instance of the struct. - /// - /// The target processing bounds for the current instance. - /// The target pixel buffer to adjust. - /// The - /// The gamma parameter to use. [MethodImpl(InliningOptions.ShortMethod)] public ApplyGammaExposureRowIntervalAction( Rectangle bounds, @@ -500,14 +477,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly Configuration configuration; private readonly float inverseGamma; - /// - /// Initializes a new instance of the struct. - /// - /// The target processing bounds for the current instance. - /// The target pixels to apply the process to. - /// The source values. Cannot be null. - /// The - /// The inverse gamma parameter to use. [MethodImpl(InliningOptions.ShortMethod)] public ApplyInverseGammaExposureRowIntervalAction( Rectangle bounds, diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index cc48ec474..410b7405c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -87,16 +87,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly Configuration configuration; private readonly bool preserveAlpha; - /// - /// Initializes a new instance of the struct. - /// - /// The target processing bounds for the current instance. - /// The target pixel buffer to adjust. - /// The source pixels. Cannot be null. - /// The vertical kernel operator. - /// The horizontal kernel operator. - /// The - /// Whether the convolution filter is applied to alpha as well as the color channels. [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalAction( Rectangle bounds, diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 65a0ace36..1be97a4f8 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -88,15 +88,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly Configuration configuration; private readonly bool preserveAlpha; - /// - /// Initializes a new instance of the struct. - /// - /// The target processing bounds for the current instance. - /// The target pixel buffer to adjust. - /// The source pixels. Cannot be null. - /// The kernel operator. - /// The - /// Whether the convolution filter is applied to alpha as well as the color channels. [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalAction( Rectangle bounds, diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 95e5107c7..db7bc5188 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -77,15 +77,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly Configuration configuration; private readonly bool preserveAlpha; - /// - /// Initializes a new instance of the struct. - /// - /// The target processing bounds for the current instance. - /// The target pixel buffer to adjust. - /// The source pixels. Cannot be null. - /// The kernel operator. - /// The - /// Whether the convolution filter is applied to alpha as well as the color channels. [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalAction( Rectangle bounds, diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index e85205c41..e2480957e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -121,15 +121,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int shiftY; private readonly int shiftX; - /// - /// Initializes a new instance of the struct. - /// - /// The target pixel buffer to adjust. - /// The processed pixels for the current iteration. Cannot be null. - /// The minimum horizontal offset. - /// The maximum horizontal offset. - /// The vertical offset shift. - /// The horizontal offset shift. [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalAction( Buffer2D targetPixels, diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 32aecbedb..402a05249 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -17,8 +17,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class AffineTransformProcessor : TransformProcessor where TPixel : struct, IPixel { - private Size targetSize; - private Matrix3x2 transformMatrix; + private readonly Size targetSize; + private readonly Matrix3x2 transformMatrix; private readonly IResampler resampler; /// @@ -58,26 +58,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { - Rectangle sourceBounds = this.SourceRectangle; - var nearestRowAction = new NearestNeighborRowIntervalAction(ref sourceBounds, ref matrix, width, source, destination); - ParallelRowIterator.IterateRows( targetBounds, configuration, - in nearestRowAction); + new NearestNeighborRowIntervalAction(this.SourceRectangle, ref matrix, width, source, destination)); return; } using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - var rowAction = new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( targetBounds, configuration, - in rowAction); + new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination)); } + /// + /// A implementing the nearest neighbor resampler logic for . + /// private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; @@ -88,7 +87,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public NearestNeighborRowIntervalAction( - ref Rectangle bounds, + Rectangle bounds, ref Matrix3x2 matrix, int maxX, ImageFrame source, @@ -101,6 +100,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.destination = destination; } + /// + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { @@ -120,6 +121,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } + /// + /// A implementing the transformation logic for . + /// private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Configuration configuration; @@ -146,6 +150,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.destination = destination; } + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows, Memory memory) { From b77a27cf0c559e06d646545774096e0fa5e55e20 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 6 Feb 2020 23:28:44 +0100 Subject: [PATCH 19/58] Refactor DrawImageProcessor --- .../GaussianBlurProcessor{TPixel}.cs | 7 +-- .../GaussianSharpenProcessor{TPixel}.cs | 7 +-- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 61 ++++++++++++++++--- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs index 3c1f82caa..3d0a7a714 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs @@ -44,10 +44,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs index f4f27a42d..506d34a3b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs @@ -44,10 +44,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); } } } diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index e435013ad..2a181174c 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Drawing @@ -100,15 +102,58 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing ParallelRowIterator.IterateRows( workingRect, configuration, - rows => + new RowIntervalAction(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity)); + } + + /// + /// A implementing the draw logic for . + /// + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly ImageFrame sourceFrame; + private readonly Image targetImage; + private readonly PixelBlender blender; + private readonly Configuration configuration; + private readonly int minX; + private readonly int width; + private readonly int locationY; + private readonly int targetX; + private readonly float opacity; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + ImageFrame sourceFrame, + Image targetImage, + PixelBlender blender, + Configuration configuration, + int minX, + int width, + int locationY, + int targetX, + float opacity) + { + this.sourceFrame = sourceFrame; + this.targetImage = targetImage; + this.blender = blender; + this.configuration = configuration; + this.minX = minX; + this.width = width; + this.locationY = locationY; + this.targetX = targetX; + this.opacity = opacity; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span background = source.GetPixelRowSpan(y).Slice(minX, width); - Span foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); - blender.Blend(configuration, background, background, foreground, this.Opacity); - } - }); + Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); + Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width); + this.blender.Blend(this.configuration, background, background, foreground, this.opacity); + } + } } } } From d5d50e941b6dc25624b00de5f26ab3c4450ce67b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 6 Feb 2020 23:40:41 +0100 Subject: [PATCH 20/58] Refactor OilPaintingProcessor --- .../Effects/OilPaintingProcessor{TPixel}.cs | 200 ++++++++++-------- 1 file changed, 111 insertions(+), 89 deletions(-) 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); + } + } } } } From f49d1b6bef83621e6972cc9c403a524ef49a0071 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 6 Feb 2020 23:55:21 +0100 Subject: [PATCH 21/58] Refactor PixelRowDelegateProcessorBase --- .../PixelRowDelegateProcessorBase{TPixel}.cs | 68 +++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs index 8926ddc66..16d8c0ed4 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs @@ -3,7 +3,9 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -35,28 +37,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startX = interest.X; - Configuration configuration = this.Configuration; - PixelConversionModifiers modifiers = this.modifiers; - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - (rows, vectorBuffer) => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - Span rowSpan = source.GetPixelRowSpan(y).Slice(startX, length); - PixelOperations.Instance.ToVector4(configuration, rowSpan, vectorSpan, modifiers); - - // Run the user defined pixel shader to the current row of pixels - this.ApplyPixelRowDelegate(vectorSpan, new Point(startX, y)); - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, rowSpan, modifiers); - } - }); + new RowIntervalAction(interest.X, source, this.Configuration, this.modifiers, this)); } /// @@ -65,5 +50,50 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// The target row of pixels to process. /// The initial horizontal and vertical offset for the input pixels to process. protected abstract void ApplyPixelRowDelegate(Span span, Point offset); + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly int startX; + private readonly ImageFrame source; + private readonly Configuration configuration; + private readonly PixelConversionModifiers modifiers; + private readonly PixelRowDelegateProcessorBase processor; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + int startX, + ImageFrame source, + Configuration configuration, + PixelConversionModifiers modifiers, + PixelRowDelegateProcessorBase processor) + { + this.startX = startX; + this.source = source; + this.configuration = configuration; + this.modifiers = modifiers; + this.processor = processor; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, vectorSpan, this.modifiers); + + // Run the user defined pixel shader to the current row of pixels + this.processor.ApplyPixelRowDelegate(vectorSpan, new Point(this.startX, y)); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan, this.modifiers); + } + } + } } } From 6d4d9a6ab1a6abc26ed41a88b4c0e9d669689594 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 00:30:46 +0100 Subject: [PATCH 22/58] Major refactor to the pixel row delegate processors --- .../Processors/Effects/IPixelRowDelegate.cs | 21 ++++++++++ .../Effects/PixelRowDelegateProcessor.cs | 30 +++++++++++++- ...RowDelegateProcessor{TPixel,TDelegate}.cs} | 40 +++++++++++-------- .../PixelRowDelegateProcessor{TPixel}.cs | 39 ------------------ .../PositionAwarePixelRowDelegateProcessor.cs | 30 +++++++++++++- ...nAwarePixelRowDelegateProcessor{TPixel}.cs | 36 ----------------- 6 files changed, 102 insertions(+), 94 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs rename src/ImageSharp/Processing/Processors/Effects/{PixelRowDelegateProcessorBase{TPixel}.cs => PixelRowDelegateProcessor{TPixel,TDelegate}.cs} (75%) delete mode 100644 src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel}.cs delete mode 100644 src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor{TPixel}.cs diff --git a/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs new file mode 100644 index 000000000..626ffd716 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Processing.Processors.Effects +{ + /// + /// An used by the row delegates for a given instance + /// + public interface IPixelRowDelegate + { + /// + /// Applies the current pixel row delegate to a target row of preprocessed pixels. + /// + /// The target row of pixels to process. + /// The initial horizontal and vertical offset for the input pixels to process. + void Invoke(Span span, Point offset); + } +} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs index 5bdc0bc80..9563f8718 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -34,6 +37,31 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) where TPixel : struct, IPixel - => new PixelRowDelegateProcessor(configuration, this, source, sourceRectangle); + { + return new PixelRowDelegateProcessor( + new PixelRowDelegate(this.PixelRowOperation), + configuration, + this.Modifiers, + source, + sourceRectangle); + } + + /// + /// A implementing the row processing logic for . + /// + public readonly struct PixelRowDelegate : IPixelRowDelegate + { + private readonly PixelRowOperation pixelRowOperation; + + [MethodImpl(InliningOptions.ShortMethod)] + public PixelRowDelegate(PixelRowOperation pixelRowOperation) + { + this.pixelRowOperation = pixelRowOperation; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(Span span, Point offset) => this.pixelRowOperation(span); + } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs similarity index 75% rename from src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index 16d8c0ed4..eea52dd71 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -14,24 +14,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// The base class for all processors that accept a user defined row processing delegate. /// /// The pixel format. - internal abstract class PixelRowDelegateProcessorBase : ImageProcessor + /// The row processor type. + internal sealed class PixelRowDelegateProcessor : ImageProcessor where TPixel : struct, IPixel + where TDelegate : struct, IPixelRowDelegate { + private readonly TDelegate rowDelegate; + /// /// The to apply during the pixel conversions. /// private readonly PixelConversionModifiers modifiers; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// The row processor to use to process each pixel row /// The configuration which allows altering default behaviour or extending the library. /// The to apply during the pixel conversions. /// The source for the current processor instance. /// The source area to process for the current processor instance. - protected PixelRowDelegateProcessorBase(Configuration configuration, PixelConversionModifiers modifiers, Image source, Rectangle sourceRectangle) + public PixelRowDelegateProcessor( + in TDelegate rowDelegate, + Configuration configuration, + PixelConversionModifiers modifiers, + Image source, + Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) - => this.modifiers = modifiers; + { + this.rowDelegate = rowDelegate; + this.modifiers = modifiers; + } /// protected override void OnFrameApply(ImageFrame source) @@ -41,18 +54,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowIntervalAction(interest.X, source, this.Configuration, this.modifiers, this)); + new RowIntervalAction(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate)); } /// - /// Applies the current pixel row delegate to a target row of preprocessed pixels. - /// - /// The target row of pixels to process. - /// The initial horizontal and vertical offset for the input pixels to process. - protected abstract void ApplyPixelRowDelegate(Span span, Point offset); - - /// - /// A implementing the convolution logic for . + /// A implementing the convolution logic for . /// private readonly struct RowIntervalAction : IRowIntervalAction { @@ -60,7 +66,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly ImageFrame source; private readonly Configuration configuration; private readonly PixelConversionModifiers modifiers; - private readonly PixelRowDelegateProcessorBase processor; + private readonly TDelegate rowProcessor; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalAction( @@ -68,13 +74,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects ImageFrame source, Configuration configuration, PixelConversionModifiers modifiers, - PixelRowDelegateProcessorBase processor) + in TDelegate rowProcessor) { this.startX = startX; this.source = source; this.configuration = configuration; this.modifiers = modifiers; - this.processor = processor; + this.rowProcessor = rowProcessor; } /// @@ -89,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects PixelOperations.Instance.ToVector4(this.configuration, rowSpan, vectorSpan, this.modifiers); // Run the user defined pixel shader to the current row of pixels - this.processor.ApplyPixelRowDelegate(vectorSpan, new Point(this.startX, y)); + this.rowProcessor.Invoke(vectorSpan, new Point(this.startX, y)); PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan, this.modifiers); } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel}.cs deleted file mode 100644 index da917eaf3..000000000 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel}.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Effects -{ - /// - /// Applies a user defined row processing delegate to the image. - /// - /// The pixel format. - internal sealed class PixelRowDelegateProcessor : PixelRowDelegateProcessorBase - where TPixel : struct, IPixel - { - /// - /// The user defined pixel row processing delegate. - /// - private readonly PixelRowOperation pixelRowOperation; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public PixelRowDelegateProcessor(Configuration configuration, PixelRowDelegateProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, definition.Modifiers, source, sourceRectangle) - { - this.pixelRowOperation = definition.PixelRowOperation; - } - - /// - protected override void ApplyPixelRowDelegate(Span span, Point offset) => this.pixelRowOperation(span); - } -} diff --git a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs index bf21f5b9b..362b15810 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -34,6 +37,31 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) where TPixel : struct, IPixel - => new PositionAwarePixelRowDelegateProcessor(configuration, this, source, sourceRectangle); + { + return new PixelRowDelegateProcessor( + new PixelRowDelegate(this.PixelRowOperation), + configuration, + this.Modifiers, + source, + sourceRectangle); + } + + /// + /// A implementing the row processing logic for . + /// + public readonly struct PixelRowDelegate : IPixelRowDelegate + { + private readonly PixelRowOperation pixelRowOperation; + + [MethodImpl(InliningOptions.ShortMethod)] + public PixelRowDelegate(PixelRowOperation pixelRowOperation) + { + this.pixelRowOperation = pixelRowOperation; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(Span span, Point offset) => this.pixelRowOperation(span, offset); + } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor{TPixel}.cs deleted file mode 100644 index 901a3a985..000000000 --- a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor{TPixel}.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Effects -{ - /// - /// Applies a user defined, position aware, row processing delegate to the image. - /// - /// The pixel format. - internal sealed class PositionAwarePixelRowDelegateProcessor : PixelRowDelegateProcessorBase - where TPixel : struct, IPixel - { - private readonly PixelRowOperation pixelRowOperation; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public PositionAwarePixelRowDelegateProcessor(Configuration configuration, PositionAwarePixelRowDelegateProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, definition.Modifiers, source, sourceRectangle) - { - this.pixelRowOperation = definition.PixelRowOperation; - } - - /// - protected override void ApplyPixelRowDelegate(Span span, Point offset) => this.pixelRowOperation(span, offset); - } -} From d39a0570dd92fb1e1310893bb510fc46f2600ea0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 01:32:59 +0100 Subject: [PATCH 23/58] Refactor FilterProcessor --- .../Filters/FilterProcessor{TPixel}.cs | 63 +++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index a8ce67af3..cdb67e48b 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -3,7 +3,9 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Filters @@ -34,27 +36,52 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startX = interest.X; - ColorMatrix matrix = this.definition.Matrix; - - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - (rows, vectorBuffer) => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - Span rowSpan = source.GetPixelRowSpan(y).Slice(startX, length); - PixelOperations.Instance.ToVector4(this.Configuration, rowSpan, vectorSpan); - - Vector4Utils.Transform(vectorSpan, ref matrix); - - PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, rowSpan); - } - }); + new RowIntervalAction(interest.X, source, this.definition.Matrix, this.Configuration)); + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly int startX; + private readonly ImageFrame source; + private readonly ColorMatrix matrix; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + int startX, + ImageFrame source, + ColorMatrix matrix, + Configuration configuration) + { + this.startX = startX; + this.source = source; + this.matrix = matrix; + this.configuration = configuration; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, vectorSpan); + + Vector4Utils.Transform(vectorSpan, ref Unsafe.AsRef(this.matrix)); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan); + } + } } } } From c6c6b9ba2e057a3704d30a7bf2a9457abf4a8185 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 7 Feb 2020 15:37:00 +1100 Subject: [PATCH 24/58] Minor perf update --- .../Convolution/BokehBlurProcessor{TPixel}.cs | 18 ++++++++++-------- .../Convolution2DProcessor{TPixel}.cs | 15 ++++++++------- .../ConvolutionProcessor{TPixel}.cs | 15 ++++++++------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index d2c547bac..834120f84 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -336,6 +336,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly Buffer2D targetValues; private readonly Buffer2D sourcePixels; private readonly Complex64[] kernel; + private readonly int maxY; + private readonly int maxX; [MethodImpl(InliningOptions.ShortMethod)] public ApplyVerticalConvolutionRowIntervalAction( @@ -345,6 +347,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Complex64[] kernel) { this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; this.targetValues = targetValues; this.sourcePixels = sourcePixels; this.kernel = kernel; @@ -354,16 +358,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { - int maxY = this.bounds.Bottom - 1; - int maxX = this.bounds.Right - 1; - for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); for (int x = 0; x < this.bounds.Width; x++) { - Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, maxY, this.bounds.X, maxX); + Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX); } } } @@ -380,6 +381,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly Complex64[] kernel; private readonly float z; private readonly float w; + private readonly int maxY; + private readonly int maxX; [MethodImpl(InliningOptions.ShortMethod)] public ApplyHorizontalConvolutionRowIntervalAction( @@ -391,6 +394,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution float w) { this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; this.targetValues = targetValues; this.sourceValues = sourceValues; this.kernel = kernel; @@ -402,16 +407,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { - int maxY = this.bounds.Bottom - 1; - int maxX = this.bounds.Right - 1; - for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); for (int x = 0; x < this.bounds.Width; x++) { - Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, maxY, this.bounds.X, maxX, this.z, this.w); + Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 410b7405c..cd550a335 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -80,6 +80,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; + private readonly int maxY; + private readonly int maxX; private readonly Buffer2D targetPixels; private readonly Buffer2D sourcePixels; private readonly DenseMatrix kernelY; @@ -98,6 +100,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution bool preserveAlpha) { this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; this.targetPixels = targetPixels; this.sourcePixels = sourcePixels; this.kernelY = kernelY; @@ -114,9 +118,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int length = vectorSpan.Length; ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); - int maxY = this.bounds.Bottom - 1; - int maxX = this.bounds.Right - 1; - for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); @@ -134,9 +135,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution y, x, this.bounds.Y, - maxY, + this.maxY, this.bounds.X, - maxX); + this.maxX); } } else @@ -151,9 +152,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution y, x, this.bounds.Y, - maxY, + this.maxY, this.bounds.X, - maxX); + this.maxX); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index db7bc5188..b68dc56e0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -71,6 +71,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; + private readonly int maxY; + private readonly int maxX; private readonly Buffer2D targetPixels; private readonly Buffer2D sourcePixels; private readonly DenseMatrix kernel; @@ -87,6 +89,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution bool preserveAlpha) { this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; this.targetPixels = targetPixels; this.sourcePixels = sourcePixels; this.kernel = kernel; @@ -102,9 +106,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int length = vectorSpan.Length; ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); - int maxY = this.bounds.Bottom - 1; - int maxX = this.bounds.Right - 1; - for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); @@ -121,9 +122,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution y, x, this.bounds.Y, - maxY, + this.maxY, this.bounds.X, - maxX); + this.maxX); } } else @@ -137,9 +138,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution y, x, this.bounds.Y, - maxY, + this.maxY, this.bounds.X, - maxX); + this.maxX); } } From e9e461ec05b668ef33a1ba940a761b8b612de84b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 7 Feb 2020 15:57:39 +1100 Subject: [PATCH 25/58] Update ProjectiveTransformProcessor{TPixel}.cs --- .../Transforms/ProjectiveTransformProcessor{TPixel}.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 76bbc3a90..5034b072f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -59,23 +59,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { Rectangle sourceBounds = this.SourceRectangle; - var nearestRowAction = new NearestNeighborRowIntervalAction(ref sourceBounds, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( targetBounds, configuration, - in nearestRowAction); + new NearestNeighborRowIntervalAction(ref sourceBounds, ref matrix, width, source, destination)); return; } using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - var rowAction = new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( targetBounds, configuration, - in rowAction); + new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination)); } private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction From d7bce16e2afe5bbd9e3110fb073fe355a4c5a22c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 7 Feb 2020 16:32:46 +1100 Subject: [PATCH 26/58] Update BackgroundColorProcessor{TPixel}.cs --- .../BackgroundColorProcessor{TPixel}.cs | 109 +++++++++--------- 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index 7423fdbfe..a9b91e837 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -27,9 +28,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// The source area to process for the current processor instance. public BackgroundColorProcessor(Configuration configuration, BackgroundColorProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) - { - this.definition = definition; - } + => this.definition = definition; /// protected override void OnFrameApply(ImageFrame source) @@ -37,65 +36,69 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays TPixel color = this.definition.Color.ToPixel(); GraphicsOptions graphicsOptions = this.definition.GraphicsOptions; - 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()); - // 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); + Configuration configuration = this.Configuration; + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } + using IMemoryOwner colors = memoryAllocator.Allocate(interest.Width); + using IMemoryOwner amount = memoryAllocator.Allocate(interest.Width); - if (minY > 0) - { - startY = 0; - } + colors.GetSpan().Fill(color); + amount.GetSpan().Fill(graphicsOptions.BlendPercentage); - int width = maxX - minX; + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - Configuration configuration = this.Configuration; - MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - - using (IMemoryOwner colors = memoryAllocator.Allocate(width)) - using (IMemoryOwner amount = memoryAllocator.Allocate(width)) - { - // Be careful! Do not capture colorSpan & amountSpan in the lambda below! - Span colorSpan = colors.GetSpan(); - Span amountSpan = amount.GetSpan(); + ParallelRowIterator.IterateRows( + interest, + configuration, + new RowIntervalAction(configuration, interest, blender, amount, colors, source)); + } - colorSpan.Fill(color); - amountSpan.Fill(graphicsOptions.BlendPercentage); + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly PixelBlender blender; + private readonly IMemoryOwner amount; + private readonly IMemoryOwner colors; + private readonly ImageFrame source; - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + Configuration configuration, + Rectangle bounds, + PixelBlender blender, + IMemoryOwner amount, + IMemoryOwner colors, + ImageFrame source) + { + this.configuration = configuration; + this.bounds = bounds; + this.blender = blender; + this.amount = amount; + this.colors = colors; + this.source = source; + } - ParallelRowIterator.IterateRows( - workingRect, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destination = - source.GetPixelRowSpan(y - startY).Slice(minX - startX, width); + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span destination = + this.source.GetPixelRowSpan(y) + .Slice(this.bounds.X, this.bounds.Width); - // This switched color & destination in the 2nd and 3rd places because we are applying the target color under the current one - blender.Blend( - configuration, - destination, - colors.GetSpan(), - destination, - amount.GetSpan()); - } - }); + // Switch color & destination in the 2nd and 3rd places because we are + // applying the target color under the current one. + this.blender.Blend( + this.configuration, + destination, + this.colors.GetSpan(), + destination, + this.amount.GetSpan()); + } } } } From c01889f11fa601db6a4623000b9eae2d6d6cab09 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 7 Feb 2020 17:23:30 +1100 Subject: [PATCH 27/58] Update GlowProcessor{TPixel}.cs --- .../Overlays/GlowProcessor{TPixel}.cs | 128 +++++++++--------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 0271caa5d..ce677c515 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -37,78 +38,83 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// protected override void OnFrameApply(ImageFrame source) { - // TODO: can we simplify the rectangle calculation? - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; TPixel glowColor = this.definition.GlowColor.ToPixel(); - Vector2 center = Rectangle.Center(this.SourceRectangle); + float blendPercent = this.definition.GraphicsOptions.BlendPercentage; - float finalRadius = this.definition.Radius.Calculate(source.Size()); + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + Vector2 center = Rectangle.Center(interest); + float finalRadius = this.definition.Radius.Calculate(interest.Size); float maxDistance = finalRadius > 0 - ? MathF.Min(finalRadius, this.SourceRectangle.Width * .5F) - : this.SourceRectangle.Width * .5F; + ? MathF.Min(finalRadius, interest.Width * .5F) + : interest.Width * .5F; - // 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); + Configuration configuration = this.Configuration; + MemoryAllocator allocator = configuration.MemoryAllocator; - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } + using IMemoryOwner rowColors = allocator.Allocate(interest.Width); + rowColors.GetSpan().Fill(glowColor); + + ParallelRowIterator.IterateRows( + interest, + configuration, + new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); + } - if (minY > 0) + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly PixelBlender blender; + private readonly Vector2 center; + private readonly float maxDistance; + private readonly float blendPercent; + private readonly IMemoryOwner colors; + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + Configuration configuration, + Rectangle bounds, + IMemoryOwner colors, + PixelBlender blender, + Vector2 center, + float maxDistance, + float blendPercent, + ImageFrame source) { - startY = 0; + this.configuration = configuration; + this.bounds = bounds; + this.colors = colors; + this.blender = blender; + this.center = center; + this.maxDistance = maxDistance; + this.blendPercent = blendPercent; + this.source = source; } - int width = maxX - minX; - int offsetX = minX - startX; - - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - - float blendPercentage = this.definition.GraphicsOptions.BlendPercentage; - Configuration configuration = this.Configuration; - MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - - using (IMemoryOwner rowColors = memoryAllocator.Allocate(width)) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) { - rowColors.GetSpan().Fill(glowColor); - - ParallelRowIterator.IterateRows( - workingRect, - configuration, - (rows, amounts) => - { - Span amountsSpan = amounts.Span; - - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - startY; - - for (int i = 0; i < width; i++) - { - float distance = Vector2.Distance(center, new Vector2(i + offsetX, offsetY)); - amountsSpan[i] = - (blendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); - } - - Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - - this.blender.Blend( - configuration, - destination, - destination, - rowColors.GetSpan(), - amountsSpan); - } - }); + Span amountsSpan = memory.Span; + + for (int y = rows.Min; y < rows.Max; y++) + { + for (int i = 0; i < this.bounds.Width; i++) + { + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + amountsSpan[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); + } + + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + this.blender.Blend( + this.configuration, + destination, + destination, + this.colors.GetSpan(), + amountsSpan); + } } } } From 4cf5920d04e3ba5c34a1e3fd093594da73b76a73 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 7 Feb 2020 21:33:15 +1100 Subject: [PATCH 28/58] Update VignetteProcessor{TPixel}.cs --- .../Overlays/VignetteProcessor{TPixel}.cs | 136 ++++++++++-------- 1 file changed, 75 insertions(+), 61 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index 55cacccdf..ee52d72cb 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -18,7 +19,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays where TPixel : struct, IPixel { private readonly PixelBlender blender; - private readonly VignetteProcessor definition; /// @@ -38,80 +38,94 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// protected override void OnFrameApply(ImageFrame source) { - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; TPixel vignetteColor = this.definition.VignetteColor.ToPixel(); - Vector2 centre = Rectangle.Center(this.SourceRectangle); + float blendPercent = this.definition.GraphicsOptions.BlendPercentage; + + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + Vector2 center = Rectangle.Center(interest); + float finalRadiusX = this.definition.RadiusX.Calculate(interest.Size); + float finalRadiusY = this.definition.RadiusY.Calculate(interest.Size); - Size sourceSize = source.Size(); - float finalRadiusX = this.definition.RadiusX.Calculate(sourceSize); - float finalRadiusY = this.definition.RadiusY.Calculate(sourceSize); float rX = finalRadiusX > 0 - ? MathF.Min(finalRadiusX, this.SourceRectangle.Width * .5F) - : this.SourceRectangle.Width * .5F; + ? MathF.Min(finalRadiusX, interest.Width * .5F) + : interest.Width * .5F; + float rY = finalRadiusY > 0 - ? MathF.Min(finalRadiusY, this.SourceRectangle.Height * .5F) - : this.SourceRectangle.Height * .5F; + ? MathF.Min(finalRadiusY, interest.Height * .5F) + : interest.Height * .5F; + float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); - // 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); + Configuration configuration = this.Configuration; + MemoryAllocator allocator = configuration.MemoryAllocator; - // Reset offset if necessary. - if (minX > 0) + using (IMemoryOwner rowColors = allocator.Allocate(interest.Width)) { - startX = 0; + rowColors.GetSpan().Fill(vignetteColor); + + ParallelRowIterator.IterateRows( + interest, + configuration, + new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); } + } - if (minY > 0) + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly PixelBlender blender; + private readonly Vector2 center; + private readonly float maxDistance; + private readonly float blendPercent; + private readonly IMemoryOwner colors; + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + Configuration configuration, + Rectangle bounds, + IMemoryOwner colors, + PixelBlender blender, + Vector2 center, + float maxDistance, + float blendPercent, + ImageFrame source) { - startY = 0; + this.configuration = configuration; + this.bounds = bounds; + this.colors = colors; + this.blender = blender; + this.center = center; + this.maxDistance = maxDistance; + this.blendPercent = blendPercent; + this.source = source; } - int width = maxX - minX; - int offsetX = minX - startX; - - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - float blendPercentage = this.definition.GraphicsOptions.BlendPercentage; - Configuration configuration = this.Configuration; - MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - - using (IMemoryOwner rowColors = memoryAllocator.Allocate(width)) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) { - rowColors.GetSpan().Fill(vignetteColor); - - ParallelRowIterator.IterateRows( - workingRect, - configuration, - (rows, amounts) => - { - Span amountsSpan = amounts.Span; - - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - startY; - - for (int i = 0; i < width; i++) - { - float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amountsSpan[i] = (blendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); - } - - Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - - this.blender.Blend( - configuration, - destination, - destination, - rowColors.GetSpan(), - amountsSpan); - } - }); + Span amountsSpan = memory.Span; + Span colorSpan = this.colors.GetSpan(); + + for (int y = rows.Min; y < rows.Max; y++) + { + for (int i = 0; i < this.bounds.Width; i++) + { + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + amountsSpan[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); + } + + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + this.blender.Blend( + this.configuration, + destination, + destination, + colorSpan, + amountsSpan); + } } } } From 57acad21f8b2b8625593a551efa280cc8573407e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 7 Feb 2020 21:33:32 +1100 Subject: [PATCH 29/58] Update GlowProcessor{TPixel}.cs --- .../Processing/Processors/Overlays/GlowProcessor{TPixel}.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index ce677c515..65a87fbf0 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -97,6 +97,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays public void Invoke(in RowInterval rows, Memory memory) { Span amountsSpan = memory.Span; + Span colorSpan = this.colors.GetSpan(); for (int y = rows.Min; y < rows.Max; y++) { @@ -112,7 +113,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays this.configuration, destination, destination, - this.colors.GetSpan(), + colorSpan, amountsSpan); } } From 70f0e9a421ca7dca13a5a01715d3d5a43e2ed296 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 7 Feb 2020 22:12:32 +1100 Subject: [PATCH 30/58] Update FlipProcessor --- .../Overlays/VignetteProcessor{TPixel}.cs | 14 +++--- .../Transforms/FlipProcessor{TPixel}.cs | 48 +++++++++++-------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index ee52d72cb..11887433c 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -60,15 +60,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays Configuration configuration = this.Configuration; MemoryAllocator allocator = configuration.MemoryAllocator; - using (IMemoryOwner rowColors = allocator.Allocate(interest.Width)) - { - rowColors.GetSpan().Fill(vignetteColor); + using IMemoryOwner rowColors = allocator.Allocate(interest.Width); + rowColors.GetSpan().Fill(vignetteColor); - ParallelRowIterator.IterateRows( - interest, - configuration, - new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); - } + ParallelRowIterator.IterateRows( + interest, + configuration, + new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); } private readonly struct RowIntervalAction : IRowIntervalAction diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs index c01cdd8ef..041f602a5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs @@ -3,7 +3,9 @@ using System; using System.Buffers; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -53,20 +55,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void FlipX(ImageFrame source, Configuration configuration) { int height = source.Height; + using IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width); + Span temp = tempBuffer.Memory.Span; - using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width)) + for (int yTop = 0; yTop < height / 2; yTop++) { - Span temp = tempBuffer.Memory.Span; - - for (int yTop = 0; yTop < height / 2; yTop++) - { - int yBottom = height - yTop - 1; - Span topRow = source.GetPixelRowSpan(yBottom); - Span bottomRow = source.GetPixelRowSpan(yTop); - topRow.CopyTo(temp); - bottomRow.CopyTo(topRow); - temp.CopyTo(bottomRow); - } + int yBottom = height - yTop - 1; + Span topRow = source.GetPixelRowSpan(yBottom); + Span bottomRow = source.GetPixelRowSpan(yTop); + topRow.CopyTo(temp); + bottomRow.CopyTo(topRow); + temp.CopyTo(bottomRow); } } @@ -80,13 +79,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( source.Bounds(), configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - source.GetPixelRowSpan(y).Reverse(); - } - }); + new RowIntervalAction(source)); + } + + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction(ImageFrame source) => this.source = source; + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + this.source.GetPixelRowSpan(y).Reverse(); + } + } } } } From e771c50ab23519f316a6677442519229e1992e99 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 7 Feb 2020 22:44:32 +1100 Subject: [PATCH 31/58] Update ResizeProcessor{TPixel}.cs --- .../Resize/ResizeProcessor{TPixel}.cs | 88 +++++++++++++------ 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 5cfbbcb48..4a986adb0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -76,7 +76,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Configuration configuration = this.Configuration; // Handle resize dimensions identical to the original - if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.targetRectangle) + if (source.Width == destination.Width + && source.Height == destination.Height + && sourceRectangle == this.targetRectangle) { // The cloned will be blank here copy all the pixel data over source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); @@ -85,14 +87,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int width = this.targetWidth; int height = this.targetHeight; - int sourceX = sourceRectangle.X; - int sourceY = sourceRectangle.Y; - int startY = this.targetRectangle.Y; - int startX = this.targetRectangle.X; - - var targetWorkingRect = Rectangle.Intersect( - this.targetRectangle, - new Rectangle(0, 0, width, height)); + var interest = Rectangle.Intersect(this.targetRectangle, new Rectangle(0, 0, width, height)); if (this.resampler is NearestNeighborResampler) { @@ -101,23 +96,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; ParallelRowIterator.IterateRows( - targetWorkingRect, + interest, configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - // Y coordinates of source points - Span sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); - Span targetRow = destination.GetPixelRowSpan(y); - - for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++) - { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; - } - } - }); + new RowIntervalAction(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination)); return; } @@ -136,12 +117,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.horizontalKernelMap, this.verticalKernelMap, width, - targetWorkingRect, + interest, this.targetRectangle.Location)) { worker.Initialize(); - var workingInterval = new RowInterval(targetWorkingRect.Top, targetWorkingRect.Bottom); + var workingInterval = new RowInterval(interest.Top, interest.Bottom); worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); } } @@ -165,5 +146,56 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.isDisposed = true; base.Dispose(disposing); } + + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly Rectangle sourceBounds; + private readonly Rectangle destinationBounds; + private readonly float widthFactor; + private readonly float heightFactor; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + Rectangle sourceBounds, + Rectangle destinationBounds, + float widthFactor, + float heightFactor, + ImageFrame source, + ImageFrame destination) + { + this.sourceBounds = sourceBounds; + this.destinationBounds = destinationBounds; + this.widthFactor = widthFactor; + this.heightFactor = heightFactor; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int sourceX = this.sourceBounds.X; + int sourceY = this.sourceBounds.Y; + int destX = this.destinationBounds.X; + int destY = this.destinationBounds.Y; + int destLeft = this.destinationBounds.Left; + int destRight = this.destinationBounds.Right; + + for (int y = rows.Min; y < rows.Max; y++) + { + // Y coordinates of source points + Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.GetPixelRowSpan(y); + + for (int x = destLeft; x < destRight; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; + } + } + } + } } } From 508381851a3e0b3dcbc444875e35bc553d3429af Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 7 Feb 2020 23:14:01 +1100 Subject: [PATCH 32/58] Seal ResizeWorker --- .../Processing/Processors/Transforms/Resize/ResizeWorker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 4f5faa38e..7cfd8e592 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// When sliding the window, the contents of the bottom window band are copied to the new top band. /// For more details, and visual explanation, see "ResizeWorker.pptx". /// - internal class ResizeWorker : IDisposable + internal sealed class ResizeWorker : IDisposable where TPixel : struct, IPixel { private readonly Buffer2D transposedFirstPassBuffer; From 57e03aebbc4524aaed1e8f1b7552f96a9f68bcdc Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 7 Feb 2020 23:16:43 +1100 Subject: [PATCH 33/58] Update RotateProcessor{TPixel}.cs --- .../Transforms/RotateProcessor{TPixel}.cs | 207 ++++++++++++------ 1 file changed, 137 insertions(+), 70 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs index 142068a48..bf03ce319 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -15,6 +17,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class RotateProcessor : AffineTransformProcessor where TPixel : struct, IPixel { + private float degrees; + /// /// Initializes a new instance of the class. /// @@ -24,11 +28,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The source area to process for the current processor instance. public RotateProcessor(Configuration configuration, RotateProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, definition, source, sourceRectangle) + => this.degrees = definition.Degrees; + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) { - this.Degrees = definition.Degrees; - } + if (this.OptimizedApply(source, destination, this.Configuration)) + { + return; + } - private float Degrees { get; } + base.OnFrameApply(source, destination); + } /// protected override void AfterImageApply(Image destination) @@ -39,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return; } - if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon) + if (MathF.Abs(WrapDegrees(this.degrees)) < Constants.Epsilon) { // No need to do anything so return. return; @@ -50,17 +61,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms base.AfterImageApply(destination); } - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - if (this.OptimizedApply(source, destination, this.Configuration)) - { - return; - } - - base.OnFrameApply(source, destination); - } - /// /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range /// @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Configuration configuration) { // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. - float degrees = WrapDegrees(this.Degrees); + float degrees = WrapDegrees(this.degrees); if (MathF.Abs(degrees) < Constants.Epsilon) { @@ -131,25 +131,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) { - int width = source.Width; - int height = source.Height; - ParallelRowIterator.IterateRows( source.Bounds(), configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = destination.GetPixelRowSpan(height - y - 1); - - for (int x = 0; x < width; x++) - { - targetRow[width - x - 1] = sourceRow[x]; - } - } - }); + new Rotate180RowIntervalAction(source.Width, source.Height, source, destination)); } /// @@ -160,31 +145,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) { - int width = source.Width; - int height = source.Height; - Rectangle destinationBounds = destination.Bounds(); - ParallelRowIterator.IterateRows( source.Bounds(), configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y); - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - newX = height - newX - 1; - int newY = width - x - 1; - - if (destinationBounds.Contains(newX, newY)) - { - destination[newX, newY] = sourceRow[x]; - } - } - } - }); + new Rotate270RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination)); } /// @@ -195,28 +159,131 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) { - int width = source.Width; - int height = source.Height; - Rectangle destinationBounds = destination.Bounds(); - ParallelRowIterator.IterateRows( source.Bounds(), configuration, - rows => + new Rotate90RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination)); + } + + private readonly struct Rotate180RowIntervalAction : IRowIntervalAction + { + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate180RowIntervalAction( + int width, + int height, + ImageFrame source, + ImageFrame destination) + { + this.width = width; + this.height = height; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); + + for (int x = 0; x < this.width; x++) + { + targetRow[this.width - x - 1] = sourceRow[x]; + } + } + } + } + + private readonly struct Rotate270RowIntervalAction : IRowIntervalAction + { + private readonly Rectangle bounds; + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate270RowIntervalAction( + Rectangle bounds, + int width, + int height, + ImageFrame source, + ImageFrame destination) + { + this.bounds = bounds; + this.width = width; + this.height = height; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + for (int x = 0; x < this.width; x++) + { + int newX = this.height - y - 1; + newX = this.height - newX - 1; + int newY = this.width - x - 1; + + if (this.bounds.Contains(newX, newY)) + { + this.destination[newX, newY] = sourceRow[x]; + } + } + } + } + } + + private readonly struct Rotate90RowIntervalAction : IRowIntervalAction + { + private readonly Rectangle bounds; + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate90RowIntervalAction( + Rectangle bounds, + int width, + int height, + ImageFrame source, + ImageFrame destination) + { + this.bounds = bounds; + this.width = width; + this.height = height; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + int newX = this.height - y - 1; + for (int x = 0; x < this.width; x++) { - for (int y = rows.Min; y < rows.Max; y++) + if (this.bounds.Contains(newX, x)) { - Span sourceRow = source.GetPixelRowSpan(y); - int newX = height - y - 1; - for (int x = 0; x < width; x++) - { - if (destinationBounds.Contains(newX, x)) - { - destination[newX, x] = sourceRow[x]; - } - } + this.destination[newX, x] = sourceRow[x]; } - }); + } + } + } } } } From add96139d63776ef5341c17715e633feba94482e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 16:00:51 +0100 Subject: [PATCH 34/58] Refactor GlobalHistogramEqualizationProcessor --- ...lHistogramEqualizationProcessor{TPixel}.cs | 163 ++++++++++++------ 1 file changed, 111 insertions(+), 52 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index ff68d0049..5d25bae82 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -49,62 +49,121 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int numberOfPixels = source.Width * source.Height; var workingRect = new Rectangle(0, 0, source.Width, source.Height); - using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); + + // Build the histogram of the grayscale levels + ParallelRowIterator.IterateRows( + workingRect, + this.Configuration, + new GrayscaleLevelsRowIntervalAction(workingRect, histogramBuffer, source, this.LuminanceLevels)); + + Span histogram = histogramBuffer.GetSpan(); + if (this.ClipHistogramEnabled) + { + this.ClipHistogram(histogram, this.ClipLimit); + } + + using IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); + + // Calculate the cumulative distribution function, which will map each input pixel to a new value. + int cdfMin = this.CalculateCdf( + ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), + ref MemoryMarshal.GetReference(histogram), + histogram.Length - 1); + + float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; + + // Apply the cdf to each pixel of the image + ParallelRowIterator.IterateRows( + workingRect, + this.Configuration, + new CdfApplicationRowIntervalAction(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); + } + + /// + /// A implementing the grayscale levels logic for . + /// + private readonly struct GrayscaleLevelsRowIntervalAction : IRowIntervalAction + { + private readonly Rectangle bounds; + private readonly IMemoryOwner histogramBuffer; + private readonly ImageFrame source; + private readonly int luminanceLevels; + + [MethodImpl(InliningOptions.ShortMethod)] + public GrayscaleLevelsRowIntervalAction( + in Rectangle bounds, + IMemoryOwner histogramBuffer, + ImageFrame source, + int luminanceLevels) { - // Build the histogram of the grayscale levels. - ParallelRowIterator.IterateRows( - workingRect, - this.Configuration, - rows => - { - ref int histogramBase = ref MemoryMarshal.GetReference(histogramBuffer.GetSpan()); - for (int y = rows.Min; y < rows.Max; y++) - { - ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); - - for (int x = 0; x < workingRect.Width; x++) - { - int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.LuminanceLevels); - Unsafe.Add(ref histogramBase, luminance)++; - } - } - }); - - Span histogram = histogramBuffer.GetSpan(); - if (this.ClipHistogramEnabled) + this.bounds = bounds; + this.histogramBuffer = histogramBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); + for (int y = rows.Min; y < rows.Max; y++) { - this.ClipHistogram(histogram, this.ClipLimit); + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) + { + int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; + } } + } + } + + /// + /// A implementing the cdf application levels logic for . + /// + private readonly struct CdfApplicationRowIntervalAction : IRowIntervalAction + { + private readonly Rectangle bounds; + private readonly IMemoryOwner cdfBuffer; + private readonly ImageFrame source; + private readonly int luminanceLevels; + private readonly float numberOfPixelsMinusCdfMin; - // Calculate the cumulative distribution function, which will map each input pixel to a new value. - int cdfMin = this.CalculateCdf( - ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), - ref MemoryMarshal.GetReference(histogram), - histogram.Length - 1); - - float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; - - // Apply the cdf to each pixel of the image - ParallelRowIterator.IterateRows( - workingRect, - this.Configuration, - rows => - { - ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); - for (int y = rows.Min; y < rows.Max; y++) - { - ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); - - for (int x = 0; x < workingRect.Width; x++) - { - ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); - int luminance = GetLuminance(pixel, this.LuminanceLevels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - } - } - }); + [MethodImpl(InliningOptions.ShortMethod)] + public CdfApplicationRowIntervalAction( + in Rectangle bounds, + IMemoryOwner cdfBuffer, + ImageFrame source, + int luminanceLevels, + float numberOfPixelsMinusCdfMin) + { + this.bounds = bounds; + this.cdfBuffer = cdfBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; + this.numberOfPixelsMinusCdfMin = numberOfPixelsMinusCdfMin; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); + for (int y = rows.Min; y < rows.Max; y++) + { + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); + int luminance = GetLuminance(pixel, this.luminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin; + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + } + } } } } From d2741422fc31c08dbd3c1becc8fe709d842669cd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 16:11:56 +0100 Subject: [PATCH 35/58] Refactor ImageFrame --- src/ImageSharp/ImageFrame{TPixel}.cs | 44 +++++++++++++++---- .../Resize/ResizeProcessor{TPixel}.cs | 2 +- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 88591af69..c3bab8e65 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -263,15 +263,7 @@ namespace SixLabors.ImageSharp ParallelRowIterator.IterateRows( this.Bounds(), configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = this.GetPixelRowSpan(y); - Span targetRow = target.GetPixelRowSpan(y); - PixelOperations.Instance.To(configuration, sourceRow, targetRow); - } - }); + new RowIntervalAction(this, target, configuration)); return target; } @@ -293,5 +285,39 @@ namespace SixLabors.ImageSharp span.Fill(value); } } + + /// + /// A implementing the clone logic for . + /// + private readonly struct RowIntervalAction : IRowIntervalAction + where TPixel2 : struct, IPixel + { + private readonly ImageFrame source; + private readonly ImageFrame target; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + ImageFrame source, + ImageFrame target, + Configuration configuration) + { + this.source = source; + this.target = target; + this.configuration = configuration; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.target.GetPixelRowSpan(y); + PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); + } + } + } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 4a986adb0..02622622d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly int targetWidth; private readonly int targetHeight; private readonly IResampler resampler; - private Rectangle targetRectangle; + private readonly Rectangle targetRectangle; private readonly bool compand; // The following fields are not immutable but are optionally created on demand. From 6b9d344cebb052708945a114edc20ddad0699db4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 16:16:40 +0100 Subject: [PATCH 36/58] Refactor BinaryThresholdProcessor --- .../BinaryThresholdProcessor{TPixel}.cs | 71 ++++++++++++++----- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 9bc7f47b1..9e9dccc46 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Binarization @@ -52,24 +54,61 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization ParallelRowIterator.IterateRows( workingRect, configuration, - rows => - { - Rgba32 rgba = default; - for (int y = rows.Min; y < rows.Max; y++) - { - Span row = source.GetPixelRowSpan(y); + new RowIntervalAction(source, upper, lower, threshold, startX, endX, isAlphaOnly)); + } - for (int x = startX; x < endX; x++) - { - ref TPixel color = ref row[x]; - color.ToRgba32(ref rgba); + /// + /// A implementing the clone logic for . + /// + private readonly struct RowIntervalAction : IRowIntervalAction + { + private readonly ImageFrame source; + private readonly TPixel upper; + private readonly TPixel lower; + private readonly byte threshold; + private readonly int startX; + private readonly int endX; + private readonly bool isAlphaOnly; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalAction( + ImageFrame source, + TPixel upper, + TPixel lower, + byte threshold, + int startX, + int endX, + bool isAlphaOnly) + { + this.source = source; + this.upper = upper; + this.lower = lower; + this.threshold = threshold; + this.startX = startX; + this.endX = endX; + this.isAlphaOnly = isAlphaOnly; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + Rgba32 rgba = default; + for (int y = rows.Min; y < rows.Max; y++) + { + Span row = this.source.GetPixelRowSpan(y); + + for (int x = this.startX; x < this.endX; x++) + { + ref TPixel color = ref row[x]; + color.ToRgba32(ref rgba); - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - color = luminance >= threshold ? upper : lower; - } - } - }); + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + color = luminance >= this.threshold ? this.upper : this.lower; + } + } + } } } } From dd7bd7b27919db9dd0432472f0e7aea3e1c53e48 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 16:57:42 +0100 Subject: [PATCH 37/58] Skip some safety readonly struct copies --- src/ImageSharp/Advanced/IRowIntervalAction.cs | 3 ++- .../Advanced/IRowIntervalAction{TBuffer}.cs | 3 ++- src/ImageSharp/Advanced/ParallelRowIterator.cs | 4 ++-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 10 ++++++++-- .../PixelFormats/PixelImplementations/Rgba64.cs | 12 ++++++------ .../Utils/Vector4Converters.RgbaCompatible.cs | 7 ++++--- .../PixelRowDelegateProcessor{TPixel,TDelegate}.cs | 2 +- .../Processors/Transforms/Resize/ResizeKernelMap.cs | 6 +++--- 8 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Advanced/IRowIntervalAction.cs b/src/ImageSharp/Advanced/IRowIntervalAction.cs index 9a67eea71..a24cda320 100644 --- a/src/ImageSharp/Advanced/IRowIntervalAction.cs +++ b/src/ImageSharp/Advanced/IRowIntervalAction.cs @@ -95,7 +95,8 @@ namespace SixLabors.ImageSharp.Advanced int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); var rows = new RowInterval(yMin, yMax); - this.action.Invoke(in rows); + // Skip the safety copy when invoking a potentially impure method on a readonly field + Unsafe.AsRef(this.action).Invoke(in rows); } } } diff --git a/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs index 1fd088e09..89ef2ab44 100644 --- a/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs @@ -92,7 +92,8 @@ namespace SixLabors.ImageSharp.Advanced var rows = new RowInterval(yMin, yMax); using IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX); - this.action.Invoke(in rows, buffer.Memory); + + Unsafe.AsRef(this.action).Invoke(in rows, buffer.Memory); } } } diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 5c1b4110e..5c8a30f68 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Advanced if (numOfSteps == 1) { var rows = new RowInterval(top, bottom); - body.Invoke(in rows); + Unsafe.AsRef(body).Invoke(in rows); return; } @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Advanced var rows = new RowInterval(top, bottom); using (IMemoryOwner buffer = allocator.Allocate(width)) { - body.Invoke(rows, buffer.Memory); + Unsafe.AsRef(body).Invoke(rows, buffer.Memory); } return; diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 69a80e024..bcbfda2ba 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -554,8 +554,14 @@ namespace SixLabors.ImageSharp.Formats.Png return; } - // Grab the palette and write it to the stream. - ReadOnlySpan palette = quantized.Palette.Span; + /* Grab the palette and write it to the stream. + * Here the palette is reinterpreted as a mutable Memory value, + * which is possible because the two memory types have the same layout. + * This is done so that the Span we're working on is mutable, + * so that we can skip the safety copies done by the compiler when we + * invoke the IPixel.ToRgba32 method below, which is not marked as readonly. */ + ReadOnlyMemory paletteMemory = quantized.Palette; + Span palette = Unsafe.As, Memory>(ref paletteMemory).Span; int paletteLength = Math.Min(palette.Length, 256); int colorTableLength = paletteLength * 3; bool anyAlpha = false; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index 56bc6f455..d71f7bca4 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -343,7 +343,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Rgba32 ToRgba32() + public readonly Rgba32 ToRgba32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -357,7 +357,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Bgra32 ToBgra32() + public readonly Bgra32 ToBgra32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -371,7 +371,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Argb32 ToArgb32() + public readonly Argb32 ToArgb32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -385,7 +385,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Rgb24 ToRgb24() + public readonly Rgb24 ToRgb24() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -398,7 +398,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Bgr24 ToBgr24() + public readonly Bgr24 ToBgr24() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 0350c669a..79574e442 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -66,7 +66,8 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils MemoryMarshal.Cast(lastQuarterOfDestBuffer), MemoryMarshal.Cast(destVectors.Slice(0, countWithoutLastItem))); - destVectors[countWithoutLastItem] = sourcePixels[countWithoutLastItem].ToVector4(); + // Reinterpret as a mutable reference to skip the safety copy of the readonly value + destVectors[countWithoutLastItem] = Unsafe.AsRef(sourcePixels[countWithoutLastItem]).ToVector4(); // TODO: Investigate optimized 1-pass approach! ApplyForwardConversionModifiers(destVectors, modifiers); @@ -126,4 +127,4 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index eea52dd71..fd725d3ba 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects PixelOperations.Instance.ToVector4(this.configuration, rowSpan, vectorSpan, this.modifiers); // Run the user defined pixel shader to the current row of pixels - this.rowProcessor.Invoke(vectorSpan, new Point(this.startX, y)); + Unsafe.AsRef(this.rowProcessor).Invoke(vectorSpan, new Point(this.startX, y)); PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan, this.modifiers); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 1b653a92c..d3d59a594 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public void Dispose() { - this.pinHandle.Dispose(); + Unsafe.AsRef(this.pinHandle).Dispose(); this.data.Dispose(); } @@ -248,4 +248,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return new ResizeKernel(left, rowPtr, length); } } -} \ No newline at end of file +} From b77dcfacf7b8df4ffe0a2e5d78685593439cb151 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 17:20:08 +0100 Subject: [PATCH 38/58] Add single row RowAction value delegate --- src/ImageSharp/Advanced/IRowAction.cs | 70 +++++++++++++++++++ .../Advanced/ParallelRowIterator.cs | 60 ++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/ImageSharp/Advanced/IRowAction.cs diff --git a/src/ImageSharp/Advanced/IRowAction.cs b/src/ImageSharp/Advanced/IRowAction.cs new file mode 100644 index 000000000..74498eb0b --- /dev/null +++ b/src/ImageSharp/Advanced/IRowAction.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row. + /// + public interface IRowAction + { + /// + /// Invokes the method passing the row y coordinate. + /// + /// The row y coordinate. + void Invoke(int y); + } + + /// + /// A that wraps a value delegate of a specified type, and info on the memory areas to process + /// + /// The type of value delegate to invoke + internal readonly struct WrappingRowAction + where T : struct, IRowAction + { + public readonly int MinY; + public readonly int MaxY; + public readonly int StepY; + public readonly int MaxX; + + private readonly T action; + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowAction(int minY, int maxY, int stepY, in T action) + : this(minY, maxY, stepY, 0, action) + { + } + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowAction(int minY, int maxY, int stepY, int maxX, in T action) + { + this.MinY = minY; + this.MaxY = maxY; + this.StepY = stepY; + this.MaxX = maxX; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.MinY + (i * this.StepY); + + if (yMin >= this.MaxY) + { + return; + } + + int yMax = Math.Min(yMin + this.StepY, this.MaxY); + + for (int y = yMin; y < yMax; y++) + { + // Skip the safety copy when invoking a potentially impure method on a readonly field + Unsafe.AsRef(this.action).Invoke(y); + } + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 5c8a30f68..275c3d10e 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -76,6 +76,66 @@ namespace SixLabors.ImageSharp.Advanced rowAction.Invoke); } + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// + /// The type of row action to perform. + /// The . + /// The to get the parallel settings from. + /// The method body defining the iteration logic on a single row. + [MethodImpl(InliningOptions.ShortMethod)] + public static void IterateRows2(Rectangle rectangle, Configuration configuration, in T body) + where T : struct, IRowAction + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows2(rectangle, in parallelSettings, in body); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// + /// The type of row action to perform. + /// The . + /// The . + /// The method body defining the iteration logic on a single row. + public static void IterateRows2( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T body) + where T : struct, IRowAction + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(body).Invoke(y); + } + + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var rowAction = new WrappingRowAction(top, bottom, verticalStep, in body); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + rowAction.Invoke); + } + /// /// Iterate through the rows of a rectangle in optimized batches defined by -s /// instantiating a temporary buffer for each invocation. From a0ad4ce3c6fc13abab1b8e6ed404302ec1d4c607 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 17:20:21 +0100 Subject: [PATCH 39/58] Refactor processors to single row logic --- src/ImageSharp/ImageFrame{TPixel}.cs | 19 ++-- .../BinaryThresholdProcessor{TPixel}.cs | 31 +++--- .../Convolution/BokehBlurProcessor{TPixel}.cs | 79 +++++++-------- .../EdgeDetectorCompassProcessor{TPixel}.cs | 39 ++++---- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 20 ++-- .../Effects/OilPaintingProcessor{TPixel}.cs | 97 +++++++++---------- ...lHistogramEqualizationProcessor{TPixel}.cs | 50 +++++----- .../BackgroundColorProcessor{TPixel}.cs | 33 +++---- .../AffineTransformProcessor{TPixel}.cs | 25 +++-- .../Transforms/CropProcessor{TPixel}.cs | 31 ++---- .../Transforms/FlipProcessor{TPixel}.cs | 19 ++-- .../ProjectiveTransformProcessor{TPixel}.cs | 29 +++--- .../Resize/ResizeProcessor{TPixel}.cs | 27 +++--- .../Transforms/RotateProcessor{TPixel}.cs | 75 +++++++------- 14 files changed, 253 insertions(+), 321 deletions(-) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index c3bab8e65..0a345db7c 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -260,10 +260,10 @@ namespace SixLabors.ImageSharp var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( this.Bounds(), configuration, - new RowIntervalAction(this, target, configuration)); + new RowAction(this, target, configuration)); return target; } @@ -289,7 +289,7 @@ namespace SixLabors.ImageSharp /// /// A implementing the clone logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction where TPixel2 : struct, IPixel { private readonly ImageFrame source; @@ -297,7 +297,7 @@ namespace SixLabors.ImageSharp private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( ImageFrame source, ImageFrame target, Configuration configuration) @@ -309,14 +309,11 @@ namespace SixLabors.ImageSharp /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = this.source.GetPixelRowSpan(y); - Span targetRow = this.target.GetPixelRowSpan(y); - PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); - } + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.target.GetPixelRowSpan(y); + PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); } } } diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 9e9dccc46..26685d75b 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Binarization @@ -51,16 +50,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( workingRect, configuration, - new RowIntervalAction(source, upper, lower, threshold, startX, endX, isAlphaOnly)); + new RowAction(source, upper, lower, threshold, startX, endX, isAlphaOnly)); } /// /// A implementing the clone logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly ImageFrame source; private readonly TPixel upper; @@ -71,7 +70,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization private readonly bool isAlphaOnly; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( ImageFrame source, TPixel upper, TPixel lower, @@ -91,22 +90,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { Rgba32 rgba = default; - for (int y = rows.Min; y < rows.Max; y++) - { - Span row = this.source.GetPixelRowSpan(y); - for (int x = this.startX; x < this.endX; x++) - { - ref TPixel color = ref row[x]; - color.ToRgba32(ref rgba); + Span row = this.source.GetPixelRowSpan(y); + + for (int x = this.startX; x < this.endX; x++) + { + ref TPixel color = ref row[x]; + color.ToRgba32(ref rgba); - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - color = luminance >= this.threshold ? this.upper : this.lower; - } + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + color = luminance >= this.threshold ? this.upper : this.lower; } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 834120f84..71647234b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -282,10 +282,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution float inverseGamma = 1 / this.gamma; // Apply the inverse gamma exposure pass, and write the final pixel data - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( this.SourceRectangle, this.Configuration, - new ApplyInverseGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); + new ApplyInverseGammaExposureRowAction(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); } /// @@ -314,23 +314,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Vector4 parameters = Unsafe.Add(ref paramsRef, i); // Compute the vertical 1D convolution - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( sourceRectangle, configuration, - new ApplyVerticalConvolutionRowIntervalAction(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); + new ApplyVerticalConvolutionRowAction(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( sourceRectangle, configuration, - new ApplyHorizontalConvolutionRowIntervalAction(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); + new ApplyHorizontalConvolutionRowAction(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); } } /// /// A implementing the vertical convolution logic for . /// - private readonly struct ApplyVerticalConvolutionRowIntervalAction : IRowIntervalAction + private readonly struct ApplyVerticalConvolutionRowAction : IRowAction { private readonly Rectangle bounds; private readonly Buffer2D targetValues; @@ -340,7 +340,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int maxX; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyVerticalConvolutionRowIntervalAction( + public ApplyVerticalConvolutionRowAction( ref Rectangle bounds, Buffer2D targetValues, Buffer2D sourcePixels, @@ -356,16 +356,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); - for (int x = 0; x < this.bounds.Width; x++) - { - Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX); - } + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX); } } } @@ -373,7 +370,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the horizontal convolution logic for . /// - private readonly struct ApplyHorizontalConvolutionRowIntervalAction : IRowIntervalAction + private readonly struct ApplyHorizontalConvolutionRowAction : IRowAction { private readonly Rectangle bounds; private readonly Buffer2D targetValues; @@ -385,7 +382,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int maxX; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyHorizontalConvolutionRowIntervalAction( + public ApplyHorizontalConvolutionRowAction( ref Rectangle bounds, Buffer2D targetValues, Buffer2D sourceValues, @@ -405,16 +402,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); - for (int x = 0; x < this.bounds.Width; x++) - { - Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w); - } + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w); } } } @@ -471,7 +465,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the inverse gamma exposure logic for . /// - private readonly struct ApplyInverseGammaExposureRowIntervalAction : IRowIntervalAction + private readonly struct ApplyInverseGammaExposureRowAction : IRowAction { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -480,7 +474,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly float inverseGamma; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyInverseGammaExposureRowIntervalAction( + public ApplyInverseGammaExposureRowAction( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourceValues, @@ -496,28 +490,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { Vector4 low = Vector4.Zero; var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); - var clamp = Vector4.Clamp(v, low, high); - v.X = MathF.Pow(clamp.X, this.inverseGamma); - v.Y = MathF.Pow(clamp.Y, this.inverseGamma); - v.Z = MathF.Pow(clamp.Z, this.inverseGamma); - } + Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); + for (int x = 0; x < this.bounds.Width; x++) + { + ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); + var clamp = Vector4.Clamp(v, low, high); + v.X = MathF.Pow(clamp.X, this.inverseGamma); + v.Y = MathF.Pow(clamp.Y, this.inverseGamma); + v.Z = MathF.Pow(clamp.Z, this.inverseGamma); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index e2480957e..6ccd9914b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -102,17 +102,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution processor.Apply(pass); } - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( Rectangle.FromLTRB(minX, minY, maxX, maxY), this.Configuration, - new RowIntervalAction(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX)); + new RowAction(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX)); } } /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly Buffer2D targetPixels; private readonly Buffer2D passPixels; @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int shiftX; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( Buffer2D targetPixels, Buffer2D passPixels, int minX, @@ -140,29 +140,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - this.shiftY; + int offsetY = y - this.shiftY; - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(offsetY)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(offsetY)); + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(offsetY)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(offsetY)); - for (int x = this.minX; x < this.maxX; x++) - { - int offsetX = x - this.shiftX; + for (int x = this.minX; x < this.maxX; x++) + { + int offsetX = x - this.shiftX; - // Grab the max components of the two pixels - ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); - ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); + // Grab the max components of the two pixels + ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); + ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); - var pixelValue = Vector4.Max( - currentPassPixel.ToVector4(), - currentTargetPixel.ToVector4()); + var pixelValue = Vector4.Max( + currentPassPixel.ToVector4(), + currentTargetPixel.ToVector4()); - currentTargetPixel.FromVector4(pixelValue); - } + currentTargetPixel.FromVector4(pixelValue); } } } diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 2a181174c..55399c5fd 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Drawing @@ -99,16 +98,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing "Cannot draw image because the source image does not overlap the target image."); } - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( workingRect, configuration, - new RowIntervalAction(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity)); + new RowAction(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity)); } /// /// A implementing the draw logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly ImageFrame sourceFrame; private readonly Image targetImage; @@ -121,7 +120,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing private readonly float opacity; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( ImageFrame sourceFrame, Image targetImage, PixelBlender blender, @@ -145,14 +144,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); - Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width); - this.blender.Blend(this.configuration, background, background, foreground, this.opacity); - } + Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); + Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width); + this.blender.Blend(this.configuration, background, background, foreground, this.opacity); } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 4abaf7ac4..728e4850d 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -47,10 +47,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects source.CopyTo(targetPixels); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( this.SourceRectangle, this.Configuration, - new RowIntervalAction(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels)); + new RowAction(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels)); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly int levels; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( Rectangle bounds, Buffer2D targetPixels, ImageFrame source, @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { int maxY = this.bounds.Bottom - 1; int maxX = this.bounds.Right - 1; @@ -117,69 +117,66 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects 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); + Span sourceRowPixelSpan = this.source.GetPixelRowSpan(y); + Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); - PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); + PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); - for (int x = this.bounds.X; x < this.bounds.Right; x++) - { - int maxIntensity = 0; - int maxIndex = 0; + for (int x = this.bounds.X; x < this.bounds.Right; x++) + { + int maxIntensity = 0; + int maxIndex = 0; - // Clear the current shared buffer before processing each target pixel - bins.Memory.Span.Clear(); + // Clear the current shared buffer before processing each target pixel + bins.Memory.Span.Clear(); - for (int fy = 0; fy <= this.radius; fy++) - { - int fyr = fy - this.radius; - int offsetY = y + fyr; + for (int fy = 0; fy <= this.radius; fy++) + { + int fyr = fy - this.radius; + int offsetY = y + fyr; - offsetY = offsetY.Clamp(0, maxY); + offsetY = offsetY.Clamp(0, maxY); - Span sourceOffsetRow = this.source.GetPixelRowSpan(offsetY); + Span sourceOffsetRow = this.source.GetPixelRowSpan(offsetY); - for (int fx = 0; fx <= this.radius; fx++) - { - int fxr = fx - this.radius; - int offsetX = x + fxr; - offsetX = offsetX.Clamp(0, maxX); + for (int fx = 0; fx <= this.radius; fx++) + { + int fxr = fx - this.radius; + int offsetX = x + fxr; + offsetX = offsetX.Clamp(0, maxX); - var vector = sourceOffsetRow[offsetX].ToVector4(); + var vector = sourceOffsetRow[offsetX].ToVector4(); - float sourceRed = vector.X; - float sourceBlue = vector.Z; - float sourceGreen = vector.Y; + float sourceRed = vector.X; + float sourceBlue = vector.Z; + float sourceGreen = vector.Y; - int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1)); + int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1)); - Unsafe.Add(ref intensityBinRef, currentIntensity)++; - Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; - Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; - Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; + Unsafe.Add(ref intensityBinRef, currentIntensity)++; + Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; + Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; + Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; - if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) - { - maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); - maxIndex = currentIntensity; - } + if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) + { + maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); + maxIndex = currentIntensity; } + } - 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; + 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; - targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); - } + targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); } + } - Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); - } + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index 5d25bae82..b6ae981fc 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -52,10 +52,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); // Build the histogram of the grayscale levels - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( workingRect, this.Configuration, - new GrayscaleLevelsRowIntervalAction(workingRect, histogramBuffer, source, this.LuminanceLevels)); + new GrayscaleLevelsRowAction(workingRect, histogramBuffer, source, this.LuminanceLevels)); Span histogram = histogramBuffer.GetSpan(); if (this.ClipHistogramEnabled) @@ -74,16 +74,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; // Apply the cdf to each pixel of the image - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( workingRect, this.Configuration, - new CdfApplicationRowIntervalAction(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); + new CdfApplicationRowAction(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); } /// /// A implementing the grayscale levels logic for . /// - private readonly struct GrayscaleLevelsRowIntervalAction : IRowIntervalAction + private readonly struct GrayscaleLevelsRowAction : IRowAction { private readonly Rectangle bounds; private readonly IMemoryOwner histogramBuffer; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly int luminanceLevels; [MethodImpl(InliningOptions.ShortMethod)] - public GrayscaleLevelsRowIntervalAction( + public GrayscaleLevelsRowAction( in Rectangle bounds, IMemoryOwner histogramBuffer, ImageFrame source, @@ -105,18 +105,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); - for (int y = rows.Min; y < rows.Max; y++) - { - ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); - for (int x = 0; x < this.bounds.Width; x++) - { - int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels); - Unsafe.Add(ref histogramBase, luminance)++; - } + for (int x = 0; x < this.bounds.Width; x++) + { + int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; } } } @@ -124,7 +121,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// A implementing the cdf application levels logic for . /// - private readonly struct CdfApplicationRowIntervalAction : IRowIntervalAction + private readonly struct CdfApplicationRowAction : IRowAction { private readonly Rectangle bounds; private readonly IMemoryOwner cdfBuffer; @@ -133,7 +130,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly float numberOfPixelsMinusCdfMin; [MethodImpl(InliningOptions.ShortMethod)] - public CdfApplicationRowIntervalAction( + public CdfApplicationRowAction( in Rectangle bounds, IMemoryOwner cdfBuffer, ImageFrame source, @@ -149,20 +146,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); - for (int y = rows.Min; y < rows.Max; y++) + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) { - ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); - - for (int x = 0; x < this.bounds.Width; x++) - { - ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); - int luminance = GetLuminance(pixel, this.luminanceLevels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin; - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - } + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); + int luminance = GetLuminance(pixel, this.luminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin; + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index a9b91e837..cb19211c2 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -49,13 +49,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( interest, configuration, - new RowIntervalAction(configuration, interest, blender, amount, colors, source)); + new RowAction(configuration, interest, blender, amount, colors, source)); } - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( Configuration configuration, Rectangle bounds, PixelBlender blender, @@ -82,23 +82,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destination = - this.source.GetPixelRowSpan(y) - .Slice(this.bounds.X, this.bounds.Width); + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - // Switch color & destination in the 2nd and 3rd places because we are - // applying the target color under the current one. - this.blender.Blend( - this.configuration, - destination, - this.colors.GetSpan(), - destination, - this.amount.GetSpan()); - } + // Switch color & destination in the 2nd and 3rd places because we are + // applying the target color under the current one. + this.blender.Blend( + this.configuration, + destination, + this.colors.GetSpan(), + destination, + this.amount.GetSpan()); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 402a05249..9bf9dc507 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -58,10 +58,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( targetBounds, configuration, - new NearestNeighborRowIntervalAction(this.SourceRectangle, ref matrix, width, source, destination)); + new NearestNeighborRowAction(this.SourceRectangle, ref matrix, width, source, destination)); return; } @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// A implementing the nearest neighbor resampler logic for . /// - private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction + private readonly struct NearestNeighborRowAction : IRowAction { private readonly Rectangle bounds; private readonly Matrix3x2 matrix; @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public NearestNeighborRowIntervalAction( + public NearestNeighborRowAction( Rectangle bounds, ref Matrix3x2 matrix, int maxX, @@ -103,19 +103,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destRow = this.destination.GetPixelRowSpan(y); + Span destRow = this.destination.GetPixelRowSpan(y); - for (int x = 0; x < this.maxX; x++) + for (int x = 0; x < this.maxX; x++) + { + var point = Point.Transform(new Point(x, y), this.matrix); + if (this.bounds.Contains(point.X, point.Y)) { - var point = Point.Transform(new Point(x, y), this.matrix); - if (this.bounds.Contains(point.X, point.Y)) - { - destRow[x] = this.source[point.X, point.Y]; - } + destRow[x] = this.source[point.X, point.Y]; } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 103c5d3ff..55b153467 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -48,34 +47,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle bounds = this.cropRectangle; // Copying is cheap, we should process more pixels per task: - ParallelExecutionSettings parallelSettings = - ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); + ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - var rowAction = new RowIntervalAction(ref bounds, source, destination); - - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( bounds, in parallelSettings, - in rowAction); + new RowAction(ref bounds, source, destination)); } /// /// A implementing the processor logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly Rectangle bounds; private readonly ImageFrame source; private readonly ImageFrame destination; - /// - /// Initializes a new instance of the struct. - /// - /// The target processing bounds for the current instance. - /// The source for the current instance. - /// The destination for the current instance. [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction(ref Rectangle bounds, ImageFrame source, ImageFrame destination) + public RowAction(ref Rectangle bounds, ImageFrame source, ImageFrame destination) { this.bounds = bounds; this.source = source; @@ -84,14 +74,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); - Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); - sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); - } + Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); + Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); + sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs index 041f602a5..b88ead08f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs @@ -5,7 +5,6 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -76,26 +75,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void FlipY(ImageFrame source, Configuration configuration) { - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( source.Bounds(), configuration, - new RowIntervalAction(source)); + new RowAction(source)); } - private readonly struct RowIntervalAction : IRowIntervalAction + /// + /// A implementing the reverse logic for . + /// + private readonly struct RowAction : IRowAction { private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction(ImageFrame source) => this.source = source; + public RowAction(ImageFrame source) => this.source = source; [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - this.source.GetPixelRowSpan(y).Reverse(); - } + this.source.GetPixelRowSpan(y).Reverse(); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 5034b072f..5a13619c5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -60,10 +60,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Rectangle sourceBounds = this.SourceRectangle; - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( targetBounds, configuration, - new NearestNeighborRowIntervalAction(ref sourceBounds, ref matrix, width, source, destination)); + new NearestNeighborRowAction(ref sourceBounds, ref matrix, width, source, destination)); return; } @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination)); } - private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction + private readonly struct NearestNeighborRowAction : IRowAction { private readonly Rectangle bounds; private readonly Matrix4x4 matrix; @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public NearestNeighborRowIntervalAction( + public NearestNeighborRowAction( ref Rectangle bounds, ref Matrix4x4 matrix, int maxX, @@ -100,22 +100,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) { - Span destRow = this.destination.GetPixelRowSpan(y); + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); - for (int x = 0; x < this.maxX; x++) + if (this.bounds.Contains(px, py)) { - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); - int px = (int)MathF.Round(point.X); - int py = (int)MathF.Round(point.Y); - - if (this.bounds.Contains(px, py)) - { - destRow[x] = this.source[px, py]; - } + destRow[x] = this.source[px, py]; } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 02622622d..027846fc4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -95,10 +95,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( interest, configuration, - new RowIntervalAction(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination)); + new RowAction(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination)); return; } @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms base.Dispose(disposing); } - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly Rectangle sourceBounds; private readonly Rectangle destinationBounds; @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( Rectangle sourceBounds, Rectangle destinationBounds, float widthFactor, @@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { int sourceX = this.sourceBounds.X; int sourceY = this.sourceBounds.Y; @@ -183,17 +183,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int destLeft = this.destinationBounds.Left; int destRight = this.destinationBounds.Right; - for (int y = rows.Min; y < rows.Max; y++) + // Y coordinates of source points + Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.GetPixelRowSpan(y); + + for (int x = destLeft; x < destRight; x++) { - // Y coordinates of source points - Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); - Span targetRow = this.destination.GetPixelRowSpan(y); - - for (int x = destLeft; x < destRight; x++) - { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; - } + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs index bf03ce319..6107c8ef9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -131,10 +131,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) { - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( source.Bounds(), configuration, - new Rotate180RowIntervalAction(source.Width, source.Height, source, destination)); + new Rotate180RowAction(source.Width, source.Height, source, destination)); } /// @@ -145,10 +145,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) { - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( source.Bounds(), configuration, - new Rotate270RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination)); + new Rotate270RowAction(destination.Bounds(), source.Width, source.Height, source, destination)); } /// @@ -159,13 +159,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) { - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( source.Bounds(), configuration, - new Rotate90RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination)); + new Rotate90RowAction(destination.Bounds(), source.Width, source.Height, source, destination)); } - private readonly struct Rotate180RowIntervalAction : IRowIntervalAction + private readonly struct Rotate180RowAction : IRowAction { private readonly int width; private readonly int height; @@ -173,7 +173,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public Rotate180RowIntervalAction( + public Rotate180RowAction( int width, int height, ImageFrame source, @@ -186,22 +186,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = this.source.GetPixelRowSpan(y); - Span targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); - for (int x = 0; x < this.width; x++) - { - targetRow[this.width - x - 1] = sourceRow[x]; - } + for (int x = 0; x < this.width; x++) + { + targetRow[this.width - x - 1] = sourceRow[x]; } } } - private readonly struct Rotate270RowIntervalAction : IRowIntervalAction + private readonly struct Rotate270RowAction : IRowAction { private readonly Rectangle bounds; private readonly int width; @@ -210,7 +207,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public Rotate270RowIntervalAction( + public Rotate270RowAction( Rectangle bounds, int width, int height, @@ -225,27 +222,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) + Span sourceRow = this.source.GetPixelRowSpan(y); + for (int x = 0; x < this.width; x++) { - Span sourceRow = this.source.GetPixelRowSpan(y); - for (int x = 0; x < this.width; x++) + int newX = this.height - y - 1; + newX = this.height - newX - 1; + int newY = this.width - x - 1; + + if (this.bounds.Contains(newX, newY)) { - int newX = this.height - y - 1; - newX = this.height - newX - 1; - int newY = this.width - x - 1; - - if (this.bounds.Contains(newX, newY)) - { - this.destination[newX, newY] = sourceRow[x]; - } + this.destination[newX, newY] = sourceRow[x]; } } } } - private readonly struct Rotate90RowIntervalAction : IRowIntervalAction + private readonly struct Rotate90RowAction : IRowAction { private readonly Rectangle bounds; private readonly int width; @@ -254,7 +248,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public Rotate90RowIntervalAction( + public Rotate90RowAction( Rectangle bounds, int width, int height, @@ -269,18 +263,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) + Span sourceRow = this.source.GetPixelRowSpan(y); + int newX = this.height - y - 1; + for (int x = 0; x < this.width; x++) { - Span sourceRow = this.source.GetPixelRowSpan(y); - int newX = this.height - y - 1; - for (int x = 0; x < this.width; x++) + if (this.bounds.Contains(newX, x)) { - if (this.bounds.Contains(newX, x)) - { - this.destination[newX, x] = sourceRow[x]; - } + this.destination[newX, x] = sourceRow[x]; } } } From d6fb30eb3ed0ab1be18d30e7561f10e1334482d8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 17:21:43 +0100 Subject: [PATCH 40/58] Refactor single row APIs --- .../Advanced/ParallelRowIterator.cs | 64 +------------------ src/ImageSharp/ImageFrame{TPixel}.cs | 2 +- .../BinaryThresholdProcessor{TPixel}.cs | 2 +- .../Convolution/BokehBlurProcessor{TPixel}.cs | 6 +- .../EdgeDetectorCompassProcessor{TPixel}.cs | 2 +- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 2 +- .../Effects/OilPaintingProcessor{TPixel}.cs | 2 +- ...lHistogramEqualizationProcessor{TPixel}.cs | 4 +- .../BackgroundColorProcessor{TPixel}.cs | 2 +- .../AffineTransformProcessor{TPixel}.cs | 2 +- .../Transforms/CropProcessor{TPixel}.cs | 2 +- .../Transforms/FlipProcessor{TPixel}.cs | 2 +- .../ProjectiveTransformProcessor{TPixel}.cs | 2 +- .../Resize/ResizeProcessor{TPixel}.cs | 2 +- .../Transforms/RotateProcessor{TPixel}.cs | 6 +- 15 files changed, 22 insertions(+), 80 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 275c3d10e..7802d9653 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -18,64 +18,6 @@ namespace SixLabors.ImageSharp.Advanced /// public static class ParallelRowIterator { - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - /// The type of row action to perform. - /// The . - /// The to get the parallel settings from. - /// The method body defining the iteration logic on a single . - [MethodImpl(InliningOptions.ShortMethod)] - public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) - where T : struct, IRowIntervalAction - { - var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, in body); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - /// The type of row action to perform. - /// The . - /// The . - /// The method body defining the iteration logic on a single . - public static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - in T body) - where T : struct, IRowIntervalAction - { - ValidateRectangle(rectangle); - - int top = rectangle.Top; - int bottom = rectangle.Bottom; - int width = rectangle.Width; - int height = rectangle.Height; - - int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - var rows = new RowInterval(top, bottom); - Unsafe.AsRef(body).Invoke(in rows); - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); - var rowAction = new WrappingRowIntervalAction(in rowInfo, in body); - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - rowAction.Invoke); - } - /// /// Iterate through the rows of a rectangle in optimized batches. /// @@ -84,11 +26,11 @@ namespace SixLabors.ImageSharp.Advanced /// The to get the parallel settings from. /// The method body defining the iteration logic on a single row. [MethodImpl(InliningOptions.ShortMethod)] - public static void IterateRows2(Rectangle rectangle, Configuration configuration, in T body) + public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) where T : struct, IRowAction { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows2(rectangle, in parallelSettings, in body); + IterateRows(rectangle, in parallelSettings, in body); } /// @@ -98,7 +40,7 @@ namespace SixLabors.ImageSharp.Advanced /// The . /// The . /// The method body defining the iteration logic on a single row. - public static void IterateRows2( + public static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, in T body) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 0a345db7c..57b8a953a 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -260,7 +260,7 @@ namespace SixLabors.ImageSharp var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( this.Bounds(), configuration, new RowAction(this, target, configuration)); diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 26685d75b..4db2b938f 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( workingRect, configuration, new RowAction(source, upper, lower, threshold, startX, endX, isAlphaOnly)); diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 71647234b..326522996 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -282,7 +282,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution float inverseGamma = 1 / this.gamma; // Apply the inverse gamma exposure pass, and write the final pixel data - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( this.SourceRectangle, this.Configuration, new ApplyInverseGammaExposureRowAction(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); @@ -314,13 +314,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Vector4 parameters = Unsafe.Add(ref paramsRef, i); // Compute the vertical 1D convolution - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( sourceRectangle, configuration, new ApplyVerticalConvolutionRowAction(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( sourceRectangle, configuration, new ApplyHorizontalConvolutionRowAction(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 6ccd9914b..85736cbd9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution processor.Apply(pass); } - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( Rectangle.FromLTRB(minX, minY, maxX, maxY), this.Configuration, new RowAction(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX)); diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 55399c5fd..a12bcb1fd 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing "Cannot draw image because the source image does not overlap the target image."); } - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( workingRect, configuration, new RowAction(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity)); diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 728e4850d..45f221c93 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects source.CopyTo(targetPixels); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( this.SourceRectangle, this.Configuration, new RowAction(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels)); diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index b6ae981fc..a4a643425 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); // Build the histogram of the grayscale levels - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( workingRect, this.Configuration, new GrayscaleLevelsRowAction(workingRect, histogramBuffer, source, this.LuminanceLevels)); @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; // Apply the cdf to each pixel of the image - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( workingRect, this.Configuration, new CdfApplicationRowAction(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index cb19211c2..2c17d71c6 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( interest, configuration, new RowAction(configuration, interest, blender, amount, colors, source)); diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 9bf9dc507..8a44dcd9b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( targetBounds, configuration, new NearestNeighborRowAction(this.SourceRectangle, ref matrix, width, source, destination)); diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 55b153467..b294ef465 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // Copying is cheap, we should process more pixels per task: ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( bounds, in parallelSettings, new RowAction(ref bounds, source, destination)); diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs index b88ead08f..91cb47891 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void FlipY(ImageFrame source, Configuration configuration) { - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( source.Bounds(), configuration, new RowAction(source)); diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 5a13619c5..6619ea892 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Rectangle sourceBounds = this.SourceRectangle; - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( targetBounds, configuration, new NearestNeighborRowAction(ref sourceBounds, ref matrix, width, source, destination)); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 027846fc4..3eeda1781 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( interest, configuration, new RowAction(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination)); diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs index 6107c8ef9..d20954d0f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) { - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( source.Bounds(), configuration, new Rotate180RowAction(source.Width, source.Height, source, destination)); @@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) { - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( source.Bounds(), configuration, new Rotate270RowAction(destination.Bounds(), source.Width, source.Height, source, destination)); @@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) { - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( source.Bounds(), configuration, new Rotate90RowAction(destination.Bounds(), source.Width, source.Height, source, destination)); From dd4285ded4248b2053321e41bef75ddb73ef1376 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 17:32:09 +0100 Subject: [PATCH 41/58] Add single row value delegate with buffer --- .../Advanced/IRowAction{TBuffer}.cs | 88 +++++++++++++++++++ .../Advanced/ParallelRowIterator.cs | 66 ++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/ImageSharp/Advanced/IRowAction{TBuffer}.cs diff --git a/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs b/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs new file mode 100644 index 000000000..4bf0d1fe4 --- /dev/null +++ b/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row with a temporary buffer. + /// + /// The type of buffer elements. + public interface IRowAction + where TBuffer : unmanaged + { + /// + /// Invokes the method passing the row and a buffer. + /// + /// The row y coordinate. + /// The contiguous region of memory. + void Invoke(int y, Span span); + } + + internal readonly struct WrappingRowAction + where T : struct, IRowAction + where TBuffer : unmanaged + { + public readonly int MinY; + public readonly int MaxY; + public readonly int StepY; + public readonly int MaxX; + + private readonly MemoryAllocator allocator; + private readonly T action; + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowAction( + int minY, + int maxY, + int stepY, + MemoryAllocator allocator, + in T action) + : this(minY, maxY, stepY, 0, allocator, action) + { + } + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowAction( + int minY, + int maxY, + int stepY, + int maxX, + MemoryAllocator allocator, + in T action) + { + this.MinY = minY; + this.MaxY = maxY; + this.StepY = stepY; + this.MaxX = maxX; + this.allocator = allocator; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.MinY + (i * this.StepY); + + if (yMin >= this.MaxY) + { + return; + } + + int yMax = Math.Min(yMin + this.StepY, this.MaxY); + + using IMemoryOwner buffer = this.allocator.Allocate(this.MaxX); + + Span span = buffer.Memory.Span; + + for (int y = yMin; y < yMax; y++) + { + Unsafe.AsRef(this.action).Invoke(y, span); + } + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 7802d9653..8bfda431a 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -141,6 +141,72 @@ namespace SixLabors.ImageSharp.Advanced rowAction.Invoke); } + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row action to perform. + /// The type of buffer elements. + /// The . + /// The to get the parallel settings from. + /// The method body defining the iteration logic on a single . + public static void IterateRows2(Rectangle rectangle, Configuration configuration, in T body) + where T : struct, IRowAction + where TBuffer : unmanaged + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows2(rectangle, in parallelSettings, in body); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + internal static void IterateRows2( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T body) + where T : struct, IRowAction + where TBuffer : unmanaged + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + using (IMemoryOwner buffer = allocator.Allocate(width)) + { + Span span = buffer.Memory.Span; + + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(body).Invoke(y, span); + } + } + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var rowAction = new WrappingRowAction(top, bottom, verticalStep, width, allocator, in body); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + rowAction.Invoke); + } + /// /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// From 3da200d02abb3db61aaca03204912d5ca0ee844c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 17:47:34 +0100 Subject: [PATCH 42/58] Refactor processors to single row with buffer APIs --- .../Convolution/BokehBlurProcessor{TPixel}.cs | 38 ++++----- .../Convolution2DProcessor{TPixel}.cs | 84 +++++++++---------- .../Convolution2PassProcessor{TPixel}.cs | 84 +++++++++---------- .../ConvolutionProcessor{TPixel}.cs | 80 +++++++++--------- ...lRowDelegateProcessor{TPixel,TDelegate}.cs | 27 +++--- .../Filters/FilterProcessor{TPixel}.cs | 25 +++--- .../Overlays/GlowProcessor{TPixel}.cs | 36 ++++---- .../Overlays/VignetteProcessor{TPixel}.cs | 38 ++++----- .../AffineTransformProcessor{TPixel}.cs | 58 ++++++------- .../ProjectiveTransformProcessor{TPixel}.cs | 69 +++++++-------- 10 files changed, 251 insertions(+), 288 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 326522996..7eb91b68d 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -268,10 +268,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution protected override void OnFrameApply(ImageFrame source) { // Preliminary gamma highlight pass - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( this.SourceRectangle, this.Configuration, - new ApplyGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); + new ApplyGammaExposureRowAction(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); // Create a 0-filled buffer to use to store the result of the component convolutions using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean); @@ -416,7 +416,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the gamma exposure logic for . /// - private readonly struct ApplyGammaExposureRowIntervalAction : IRowIntervalAction + private readonly struct ApplyGammaExposureRowAction : IRowAction { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -424,7 +424,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly float gamma; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyGammaExposureRowIntervalAction( + public ApplyGammaExposureRowAction( Rectangle bounds, Buffer2D targetPixels, Configuration configuration, @@ -438,27 +438,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(int y, Span span) { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; + int length = span.Length; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan); - - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, x); - v.X = MathF.Pow(v.X, this.gamma); - v.Y = MathF.Pow(v.Y, this.gamma); - v.Z = MathF.Pow(v.Z, this.gamma); - } + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span, PixelConversionModifiers.Premultiply); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan.Slice(0, length), targetRowSpan); + for (int x = 0; x < this.bounds.Width; x++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, x); + v.X = MathF.Pow(v.X, this.gamma); + v.Y = MathF.Pow(v.Y, this.gamma); + v.Z = MathF.Pow(v.Z, this.gamma); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span.Slice(0, length), targetRowSpan); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index cd550a335..c169ef38d 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -66,10 +66,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( interest, this.Configuration, - new RowIntervalAction(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha)); + new RowAction(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha)); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly Rectangle bounds; private readonly int maxY; @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, @@ -112,54 +112,50 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(int y, Span span) { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + int length = span.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(span); - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span); - if (this.preserveAlpha) + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve2D3( - in this.kernelY, - in this.kernelX, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); - } + DenseMatrixUtils.Convolve2D3( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); } - else + } + else + { + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve2D4( - in this.kernelY, - in this.kernelX, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); - } + DenseMatrixUtils.Convolve2D4( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 1be97a4f8..156c20d38 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -64,22 +64,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // Horizontal convolution - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( interest, this.Configuration, - new RowIntervalAction(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); + new RowAction(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); // Vertical convolution - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( interest, this.Configuration, - new RowIntervalAction(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); + new RowAction(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); } /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, @@ -107,55 +107,51 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(int y, Span span) { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + int length = span.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(span); int maxY = this.bounds.Bottom - 1; int maxX = this.bounds.Right - 1; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span); - if (this.preserveAlpha) + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve3( - in this.kernel, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - maxY, - this.bounds.X, - maxX); - } + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); } - else + } + else + { + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve4( - in this.kernel, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - maxY, - this.bounds.X, - maxX); - } + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index b68dc56e0..6c56af6bb 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -57,10 +57,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( interest, this.Configuration, - new RowIntervalAction(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha)); + new RowAction(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha)); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly Rectangle bounds; private readonly int maxY; @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, @@ -100,52 +100,48 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(int y, Span span) { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + int length = span.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(span); - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span); - if (this.preserveAlpha) + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve3( - in this.kernel, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); - } + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); } - else + } + else + { + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve4( - in this.kernel, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); - } + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index fd725d3ba..99e0341b4 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -5,7 +5,6 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -51,16 +50,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( interest, this.Configuration, - new RowIntervalAction(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate)); + new RowAction(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate)); } /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly int startX; private readonly ImageFrame source; @@ -69,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly TDelegate rowProcessor; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( int startX, ImageFrame source, Configuration configuration, @@ -85,20 +84,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(int y, Span span) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, vectorSpan, this.modifiers); + int length = span.Length; + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers); - // Run the user defined pixel shader to the current row of pixels - Unsafe.AsRef(this.rowProcessor).Invoke(vectorSpan, new Point(this.startX, y)); + // Run the user defined pixel shader to the current row of pixels + Unsafe.AsRef(this.rowProcessor).Invoke(span, new Point(this.startX, y)); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan, this.modifiers); - } + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, this.modifiers); } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index cdb67e48b..2426765f8 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -5,7 +5,6 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Filters @@ -37,16 +36,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( interest, this.Configuration, - new RowIntervalAction(interest.X, source, this.definition.Matrix, this.Configuration)); + new RowAction(interest.X, source, this.definition.Matrix, this.Configuration)); } /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly int startX; private readonly ImageFrame source; @@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( int startX, ImageFrame source, ColorMatrix matrix, @@ -68,19 +67,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(int y, Span span) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, vectorSpan); + int length = span.Length; + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span); - Vector4Utils.Transform(vectorSpan, ref Unsafe.AsRef(this.matrix)); + Vector4Utils.Transform(span, ref Unsafe.AsRef(this.matrix)); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan); - } + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 65a87fbf0..1b321310d 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -55,13 +55,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(glowColor); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( interest, configuration, - new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); + new RowAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); } - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( Configuration configuration, Rectangle bounds, IMemoryOwner colors, @@ -94,28 +94,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(int y, Span span) { - Span amountsSpan = memory.Span; Span colorSpan = this.colors.GetSpan(); - for (int y = rows.Min; y < rows.Max; y++) + for (int i = 0; i < this.bounds.Width; i++) { - for (int i = 0; i < this.bounds.Width; i++) - { - float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); - amountsSpan[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); - } + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + span[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); + } - Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - this.blender.Blend( - this.configuration, - destination, - destination, - colorSpan, - amountsSpan); - } + this.blender.Blend( + this.configuration, + destination, + destination, + colorSpan, + span); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index 11887433c..1ca83190c 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -63,13 +63,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(vignetteColor); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( interest, configuration, - new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); + new RowAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); } - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( Configuration configuration, Rectangle bounds, IMemoryOwner colors, @@ -102,28 +102,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(int y, Span span) { - Span amountsSpan = memory.Span; Span colorSpan = this.colors.GetSpan(); - for (int y = rows.Min; y < rows.Max; y++) + for (int i = 0; i < this.bounds.Width; i++) { - for (int i = 0; i < this.bounds.Width; i++) - { - float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); - amountsSpan[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); - } - - Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - - this.blender.Blend( - this.configuration, - destination, - destination, - colorSpan, - amountsSpan); + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + span[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); } + + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + this.blender.Blend( + this.configuration, + destination, + destination, + colorSpan, + span); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 8a44dcd9b..e7f6f429c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -5,7 +5,6 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -68,10 +67,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( targetBounds, configuration, - new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination)); + new RowAction(configuration, kernelMap, ref matrix, width, source, destination)); } /// @@ -101,7 +100,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } /// - /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { @@ -121,7 +119,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// A implementing the transformation logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowAction : IRowAction { private readonly Configuration configuration; private readonly TransformKernelMap kernelMap; @@ -131,7 +129,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( Configuration configuration, TransformKernelMap kernelMap, ref Matrix3x2 matrix, @@ -149,35 +147,31 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(int y, Span span) { - Span vectorSpan = memory.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, vectorSpan); - ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); - ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); - - for (int x = 0; x < this.maxX; x++) - { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - var point = Vector2.Transform(new Vector2(x, y), this.matrix); - this.kernelMap.Convolve( - point, - x, - ref ySpanRef, - ref xSpanRef, - this.source.PixelBuffer, - vectorSpan); - } + Span targetRowSpan = this.destination.GetPixelRowSpan(y); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, span); + ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); + ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - vectorSpan, - targetRowSpan); + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var point = Vector2.Transform(new Vector2(x, y), this.matrix); + this.kernelMap.Convolve( + point, + x, + ref ySpanRef, + ref xSpanRef, + this.source.PixelBuffer, + span); } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 6619ea892..8e219f7cc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -5,7 +5,6 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -17,9 +16,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class ProjectiveTransformProcessor : TransformProcessor where TPixel : struct, IPixel { - private Size targetSize; + private readonly Size targetSize; private readonly IResampler resampler; - private Matrix4x4 transformMatrix; + private readonly Matrix4x4 transformMatrix; /// /// Initializes a new instance of the class. @@ -70,12 +69,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows2( targetBounds, configuration, - new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination)); + new RowAction(configuration, kernelMap, ref matrix, width, source, destination)); } + /// + /// A implementing the nearest neighbor interpolation logic for . + /// private readonly struct NearestNeighborRowAction : IRowAction { private readonly Rectangle bounds; @@ -99,6 +101,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.destination = destination; } + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { @@ -118,7 +121,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } - private readonly struct RowIntervalAction : IRowIntervalAction + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowAction : IRowAction { private readonly Configuration configuration; private readonly TransformKernelMap kernelMap; @@ -128,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowAction( Configuration configuration, TransformKernelMap kernelMap, ref Matrix4x4 matrix, @@ -144,36 +150,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.destination = destination; } + /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(int y, Span span) { - Span vectorSpan = memory.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, vectorSpan); - ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); - ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); - - for (int x = 0; x < this.maxX; x++) - { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); - this.kernelMap.Convolve( - point, - x, - ref ySpanRef, - ref xSpanRef, - this.source.PixelBuffer, - vectorSpan); - } + Span targetRowSpan = this.destination.GetPixelRowSpan(y); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, span); + ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); + ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - vectorSpan, - targetRowSpan); + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); + this.kernelMap.Convolve( + point, + x, + ref ySpanRef, + ref xSpanRef, + this.source.PixelBuffer, + span); } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + targetRowSpan); } } } From b545ea0979495b7ea1117bc6b7517b2c5091e18d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Feb 2020 17:48:12 +0100 Subject: [PATCH 43/58] Rename APIs --- .../Advanced/ParallelRowIterator.cs | 65 +------------------ .../Convolution/BokehBlurProcessor{TPixel}.cs | 2 +- .../Convolution2DProcessor{TPixel}.cs | 2 +- .../Convolution2PassProcessor{TPixel}.cs | 4 +- .../ConvolutionProcessor{TPixel}.cs | 2 +- ...lRowDelegateProcessor{TPixel,TDelegate}.cs | 2 +- .../Filters/FilterProcessor{TPixel}.cs | 2 +- .../Overlays/GlowProcessor{TPixel}.cs | 2 +- .../Overlays/VignetteProcessor{TPixel}.cs | 2 +- .../AffineTransformProcessor{TPixel}.cs | 2 +- .../ProjectiveTransformProcessor{TPixel}.cs | 2 +- 11 files changed, 12 insertions(+), 75 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 8bfda431a..d57e673c0 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Advanced /// The to get the parallel settings from. /// The method body defining the iteration logic on a single . public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) - where T : struct, IRowIntervalAction + where T : struct, IRowAction where TBuffer : unmanaged { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); @@ -100,69 +100,6 @@ namespace SixLabors.ImageSharp.Advanced /// instantiating a temporary buffer for each invocation. /// internal static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - in T body) - where T : struct, IRowIntervalAction - where TBuffer : unmanaged - { - ValidateRectangle(rectangle); - - int top = rectangle.Top; - int bottom = rectangle.Bottom; - int width = rectangle.Width; - int height = rectangle.Height; - - int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - MemoryAllocator allocator = parallelSettings.MemoryAllocator; - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - var rows = new RowInterval(top, bottom); - using (IMemoryOwner buffer = allocator.Allocate(width)) - { - Unsafe.AsRef(body).Invoke(rows, buffer.Memory); - } - - return; - } - - int verticalStep = DivideCeil(height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); - var rowAction = new WrappingRowIntervalBufferAction(in rowInfo, allocator, in body); - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - rowAction.Invoke); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - /// The type of row action to perform. - /// The type of buffer elements. - /// The . - /// The to get the parallel settings from. - /// The method body defining the iteration logic on a single . - public static void IterateRows2(Rectangle rectangle, Configuration configuration, in T body) - where T : struct, IRowAction - where TBuffer : unmanaged - { - var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows2(rectangle, in parallelSettings, in body); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - internal static void IterateRows2( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, in T body) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 7eb91b68d..05508c90f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -268,7 +268,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution protected override void OnFrameApply(ImageFrame source) { // Preliminary gamma highlight pass - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( this.SourceRectangle, this.Configuration, new ApplyGammaExposureRowAction(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index c169ef38d..a6d47fa58 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( interest, this.Configuration, new RowAction(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha)); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 156c20d38..1c6ca8b92 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -64,13 +64,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // Horizontal convolution - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( interest, this.Configuration, new RowAction(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); // Vertical convolution - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( interest, this.Configuration, new RowAction(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 6c56af6bb..6a2acf770 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( interest, this.Configuration, new RowAction(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha)); diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index 99e0341b4..c0f479756 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( interest, this.Configuration, new RowAction(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate)); diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index 2426765f8..c797c1358 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( interest, this.Configuration, new RowAction(interest.X, source, this.definition.Matrix, this.Configuration)); diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 1b321310d..6777e3234 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(glowColor); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( interest, configuration, new RowAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index 1ca83190c..6e2c3c442 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(vignetteColor); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( interest, configuration, new RowAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index e7f6f429c..447a99eec 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( targetBounds, configuration, new RowAction(configuration, kernelMap, ref matrix, width, source, destination)); diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 8e219f7cc..5989f2389 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - ParallelRowIterator.IterateRows2( + ParallelRowIterator.IterateRows( targetBounds, configuration, new RowAction(configuration, kernelMap, ref matrix, width, source, destination)); From fcf7c44a63ab1a4a39c32e1ae19ace3821d431bf Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 8 Feb 2020 21:29:06 +1100 Subject: [PATCH 44/58] 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 cff6e3b60..983a1eb8b 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 df85afc5e..506cea8da 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; } - }); + } + } + } } } } From 8540f83216cb92f2c2a61796cf473e1fb7b39d3e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 8 Feb 2020 22:13:17 +1100 Subject: [PATCH 45/58] Revert "Merge pull request #1108 from SixLabors/sp/single-row-parallel-value-delegate" This reverts commit 1835475ad8cd3e2353e7c15065eb9c832394d535, reversing changes made to dd7bd7b27919db9dd0432472f0e7aea3e1c53e48. --- src/ImageSharp/Advanced/IRowAction.cs | 70 ----------- .../Advanced/IRowAction{TBuffer}.cs | 88 -------------- .../Advanced/ParallelRowIterator.cs | 37 +++--- src/ImageSharp/ImageFrame{TPixel}.cs | 17 +-- .../BinaryThresholdProcessor{TPixel}.cs | 29 +++-- .../Convolution/BokehBlurProcessor{TPixel}.cs | 111 ++++++++++-------- .../Convolution2DProcessor{TPixel}.cs | 84 ++++++------- .../Convolution2PassProcessor{TPixel}.cs | 84 ++++++------- .../ConvolutionProcessor{TPixel}.cs | 80 +++++++------ .../EdgeDetectorCompassProcessor{TPixel}.cs | 37 +++--- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 18 +-- .../Effects/OilPaintingProcessor{TPixel}.cs | 95 +++++++-------- ...lRowDelegateProcessor{TPixel,TDelegate}.cs | 27 +++-- .../Filters/FilterProcessor{TPixel}.cs | 25 ++-- ...lHistogramEqualizationProcessor{TPixel}.cs | 46 ++++---- .../BackgroundColorProcessor{TPixel}.cs | 31 +++-- .../Overlays/GlowProcessor{TPixel}.cs | 36 +++--- .../Overlays/VignetteProcessor{TPixel}.cs | 38 +++--- .../AffineTransformProcessor{TPixel}.cs | 81 +++++++------ .../Transforms/CropProcessor{TPixel}.cs | 29 +++-- .../Transforms/FlipProcessor{TPixel}.cs | 17 +-- .../ProjectiveTransformProcessor{TPixel}.cs | 96 +++++++-------- .../Resize/ResizeProcessor{TPixel}.cs | 25 ++-- .../Transforms/RotateProcessor{TPixel}.cs | 69 ++++++----- 24 files changed, 606 insertions(+), 664 deletions(-) delete mode 100644 src/ImageSharp/Advanced/IRowAction.cs delete mode 100644 src/ImageSharp/Advanced/IRowAction{TBuffer}.cs diff --git a/src/ImageSharp/Advanced/IRowAction.cs b/src/ImageSharp/Advanced/IRowAction.cs deleted file mode 100644 index 74498eb0b..000000000 --- a/src/ImageSharp/Advanced/IRowAction.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Advanced -{ - /// - /// Defines the contract for an action that operates on a row. - /// - public interface IRowAction - { - /// - /// Invokes the method passing the row y coordinate. - /// - /// The row y coordinate. - void Invoke(int y); - } - - /// - /// A that wraps a value delegate of a specified type, and info on the memory areas to process - /// - /// The type of value delegate to invoke - internal readonly struct WrappingRowAction - where T : struct, IRowAction - { - public readonly int MinY; - public readonly int MaxY; - public readonly int StepY; - public readonly int MaxX; - - private readonly T action; - - [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowAction(int minY, int maxY, int stepY, in T action) - : this(minY, maxY, stepY, 0, action) - { - } - - [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowAction(int minY, int maxY, int stepY, int maxX, in T action) - { - this.MinY = minY; - this.MaxY = maxY; - this.StepY = stepY; - this.MaxX = maxX; - this.action = action; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) - { - int yMin = this.MinY + (i * this.StepY); - - if (yMin >= this.MaxY) - { - return; - } - - int yMax = Math.Min(yMin + this.StepY, this.MaxY); - - for (int y = yMin; y < yMax; y++) - { - // Skip the safety copy when invoking a potentially impure method on a readonly field - Unsafe.AsRef(this.action).Invoke(y); - } - } - } -} diff --git a/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs b/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs deleted file mode 100644 index 4bf0d1fe4..000000000 --- a/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Advanced -{ - /// - /// Defines the contract for an action that operates on a row with a temporary buffer. - /// - /// The type of buffer elements. - public interface IRowAction - where TBuffer : unmanaged - { - /// - /// Invokes the method passing the row and a buffer. - /// - /// The row y coordinate. - /// The contiguous region of memory. - void Invoke(int y, Span span); - } - - internal readonly struct WrappingRowAction - where T : struct, IRowAction - where TBuffer : unmanaged - { - public readonly int MinY; - public readonly int MaxY; - public readonly int StepY; - public readonly int MaxX; - - private readonly MemoryAllocator allocator; - private readonly T action; - - [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowAction( - int minY, - int maxY, - int stepY, - MemoryAllocator allocator, - in T action) - : this(minY, maxY, stepY, 0, allocator, action) - { - } - - [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowAction( - int minY, - int maxY, - int stepY, - int maxX, - MemoryAllocator allocator, - in T action) - { - this.MinY = minY; - this.MaxY = maxY; - this.StepY = stepY; - this.MaxX = maxX; - this.allocator = allocator; - this.action = action; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) - { - int yMin = this.MinY + (i * this.StepY); - - if (yMin >= this.MaxY) - { - return; - } - - int yMax = Math.Min(yMin + this.StepY, this.MaxY); - - using IMemoryOwner buffer = this.allocator.Allocate(this.MaxX); - - Span span = buffer.Memory.Span; - - for (int y = yMin; y < yMax; y++) - { - Unsafe.AsRef(this.action).Invoke(y, span); - } - } - } -} diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index d57e673c0..5c8a30f68 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -19,32 +19,32 @@ namespace SixLabors.ImageSharp.Advanced public static class ParallelRowIterator { /// - /// Iterate through the rows of a rectangle in optimized batches. + /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// /// The type of row action to perform. /// The . /// The to get the parallel settings from. - /// The method body defining the iteration logic on a single row. + /// The method body defining the iteration logic on a single . [MethodImpl(InliningOptions.ShortMethod)] public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) - where T : struct, IRowAction + where T : struct, IRowIntervalAction { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); IterateRows(rectangle, in parallelSettings, in body); } /// - /// Iterate through the rows of a rectangle in optimized batches. + /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// /// The type of row action to perform. /// The . /// The . - /// The method body defining the iteration logic on a single row. + /// The method body defining the iteration logic on a single . public static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, in T body) - where T : struct, IRowAction + where T : struct, IRowIntervalAction { ValidateRectangle(rectangle); @@ -59,17 +59,15 @@ namespace SixLabors.ImageSharp.Advanced // Avoid TPL overhead in this trivial case: if (numOfSteps == 1) { - for (int y = top; y < bottom; y++) - { - Unsafe.AsRef(body).Invoke(y); - } - + var rows = new RowInterval(top, bottom); + Unsafe.AsRef(body).Invoke(in rows); return; } int verticalStep = DivideCeil(rectangle.Height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var rowAction = new WrappingRowAction(top, bottom, verticalStep, in body); + var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); + var rowAction = new WrappingRowIntervalAction(in rowInfo, in body); Parallel.For( 0, @@ -88,7 +86,7 @@ namespace SixLabors.ImageSharp.Advanced /// The to get the parallel settings from. /// The method body defining the iteration logic on a single . public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) - where T : struct, IRowAction + where T : struct, IRowIntervalAction where TBuffer : unmanaged { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); @@ -103,7 +101,7 @@ namespace SixLabors.ImageSharp.Advanced Rectangle rectangle, in ParallelExecutionSettings parallelSettings, in T body) - where T : struct, IRowAction + where T : struct, IRowIntervalAction where TBuffer : unmanaged { ValidateRectangle(rectangle); @@ -120,14 +118,10 @@ namespace SixLabors.ImageSharp.Advanced // Avoid TPL overhead in this trivial case: if (numOfSteps == 1) { + var rows = new RowInterval(top, bottom); using (IMemoryOwner buffer = allocator.Allocate(width)) { - Span span = buffer.Memory.Span; - - for (int y = top; y < bottom; y++) - { - Unsafe.AsRef(body).Invoke(y, span); - } + Unsafe.AsRef(body).Invoke(rows, buffer.Memory); } return; @@ -135,7 +129,8 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var rowAction = new WrappingRowAction(top, bottom, verticalStep, width, allocator, in body); + var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); + var rowAction = new WrappingRowIntervalBufferAction(in rowInfo, allocator, in body); Parallel.For( 0, diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 57b8a953a..c3bab8e65 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -263,7 +263,7 @@ namespace SixLabors.ImageSharp ParallelRowIterator.IterateRows( this.Bounds(), configuration, - new RowAction(this, target, configuration)); + new RowIntervalAction(this, target, configuration)); return target; } @@ -289,7 +289,7 @@ namespace SixLabors.ImageSharp /// /// A implementing the clone logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction where TPixel2 : struct, IPixel { private readonly ImageFrame source; @@ -297,7 +297,7 @@ namespace SixLabors.ImageSharp private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( ImageFrame source, ImageFrame target, Configuration configuration) @@ -309,11 +309,14 @@ namespace SixLabors.ImageSharp /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - Span sourceRow = this.source.GetPixelRowSpan(y); - Span targetRow = this.target.GetPixelRowSpan(y); - PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.target.GetPixelRowSpan(y); + PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 4db2b938f..9e9dccc46 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Binarization @@ -53,13 +54,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization ParallelRowIterator.IterateRows( workingRect, configuration, - new RowAction(source, upper, lower, threshold, startX, endX, isAlphaOnly)); + new RowIntervalAction(source, upper, lower, threshold, startX, endX, isAlphaOnly)); } /// /// A implementing the clone logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly ImageFrame source; private readonly TPixel upper; @@ -70,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization private readonly bool isAlphaOnly; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( ImageFrame source, TPixel upper, TPixel lower, @@ -90,20 +91,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { Rgba32 rgba = default; - - Span row = this.source.GetPixelRowSpan(y); - - for (int x = this.startX; x < this.endX; x++) + for (int y = rows.Min; y < rows.Max; y++) { - ref TPixel color = ref row[x]; - color.ToRgba32(ref rgba); + Span row = this.source.GetPixelRowSpan(y); + + for (int x = this.startX; x < this.endX; x++) + { + ref TPixel color = ref row[x]; + color.ToRgba32(ref rgba); - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - color = luminance >= this.threshold ? this.upper : this.lower; + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + color = luminance >= this.threshold ? this.upper : this.lower; + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 05508c90f..834120f84 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -268,10 +268,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution protected override void OnFrameApply(ImageFrame source) { // Preliminary gamma highlight pass - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( this.SourceRectangle, this.Configuration, - new ApplyGammaExposureRowAction(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); + new ApplyGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); // Create a 0-filled buffer to use to store the result of the component convolutions using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean); @@ -285,7 +285,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution ParallelRowIterator.IterateRows( this.SourceRectangle, this.Configuration, - new ApplyInverseGammaExposureRowAction(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); + new ApplyInverseGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); } /// @@ -317,20 +317,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution ParallelRowIterator.IterateRows( sourceRectangle, configuration, - new ApplyVerticalConvolutionRowAction(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); + new ApplyVerticalConvolutionRowIntervalAction(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer ParallelRowIterator.IterateRows( sourceRectangle, configuration, - new ApplyHorizontalConvolutionRowAction(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); + new ApplyHorizontalConvolutionRowIntervalAction(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); } } /// /// A implementing the vertical convolution logic for . /// - private readonly struct ApplyVerticalConvolutionRowAction : IRowAction + private readonly struct ApplyVerticalConvolutionRowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly Buffer2D targetValues; @@ -340,7 +340,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int maxX; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyVerticalConvolutionRowAction( + public ApplyVerticalConvolutionRowIntervalAction( ref Rectangle bounds, Buffer2D targetValues, Buffer2D sourcePixels, @@ -356,13 +356,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); - - for (int x = 0; x < this.bounds.Width; x++) + for (int y = rows.Min; y < rows.Max; y++) { - Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); + + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX); + } } } } @@ -370,7 +373,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the horizontal convolution logic for . /// - private readonly struct ApplyHorizontalConvolutionRowAction : IRowAction + private readonly struct ApplyHorizontalConvolutionRowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly Buffer2D targetValues; @@ -382,7 +385,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int maxX; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyHorizontalConvolutionRowAction( + public ApplyHorizontalConvolutionRowIntervalAction( ref Rectangle bounds, Buffer2D targetValues, Buffer2D sourceValues, @@ -402,13 +405,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); - - for (int x = 0; x < this.bounds.Width; x++) + for (int y = rows.Min; y < rows.Max; y++) { - Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); + + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w); + } } } } @@ -416,7 +422,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the gamma exposure logic for . /// - private readonly struct ApplyGammaExposureRowAction : IRowAction + private readonly struct ApplyGammaExposureRowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -424,7 +430,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly float gamma; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyGammaExposureRowAction( + public ApplyGammaExposureRowIntervalAction( Rectangle bounds, Buffer2D targetPixels, Configuration configuration, @@ -438,30 +444,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + public void Invoke(in RowInterval rows, Memory memory) { - int length = span.Length; + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); - - for (int x = 0; x < this.bounds.Width; x++) + for (int y = rows.Min; y < rows.Max; y++) { - ref Vector4 v = ref Unsafe.Add(ref baseRef, x); - v.X = MathF.Pow(v.X, this.gamma); - v.Y = MathF.Pow(v.Y, this.gamma); - v.Z = MathF.Pow(v.Z, this.gamma); - } + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span.Slice(0, length), targetRowSpan); + for (int x = 0; x < this.bounds.Width; x++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, x); + v.X = MathF.Pow(v.X, this.gamma); + v.Y = MathF.Pow(v.Y, this.gamma); + v.Z = MathF.Pow(v.Z, this.gamma); + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan.Slice(0, length), targetRowSpan); + } } } /// /// A implementing the inverse gamma exposure logic for . /// - private readonly struct ApplyInverseGammaExposureRowAction : IRowAction + private readonly struct ApplyInverseGammaExposureRowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -470,7 +480,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly float inverseGamma; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyInverseGammaExposureRowAction( + public ApplyInverseGammaExposureRowIntervalAction( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourceValues, @@ -486,25 +496,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { Vector4 low = Vector4.Zero; var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - - for (int x = 0; x < this.bounds.Width; x++) + for (int y = rows.Min; y < rows.Max; y++) { - ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); - var clamp = Vector4.Clamp(v, low, high); - v.X = MathF.Pow(clamp.X, this.inverseGamma); - v.Y = MathF.Pow(clamp.Y, this.inverseGamma); - v.Z = MathF.Pow(clamp.Z, this.inverseGamma); - } + Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); + for (int x = 0; x < this.bounds.Width; x++) + { + ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); + var clamp = Vector4.Clamp(v, low, high); + v.X = MathF.Pow(clamp.X, this.inverseGamma); + v.Y = MathF.Pow(clamp.Y, this.inverseGamma); + v.Z = MathF.Pow(clamp.Z, this.inverseGamma); + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index a6d47fa58..cd550a335 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -66,10 +66,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowAction(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha)); + new RowIntervalAction(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha)); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the convolution logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly int maxY; @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, @@ -112,50 +112,54 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + public void Invoke(in RowInterval rows, Memory memory) { - int length = span.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(span); + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span); - - if (this.preserveAlpha) + for (int y = rows.Min; y < rows.Max; y++) { - for (int x = 0; x < this.bounds.Width; x++) + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + + if (this.preserveAlpha) { - DenseMatrixUtils.Convolve2D3( - in this.kernelY, - in this.kernelX, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve2D3( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } } - } - else - { - for (int x = 0; x < this.bounds.Width; x++) + else { - DenseMatrixUtils.Convolve2D4( - in this.kernelY, - in this.kernelX, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve2D4( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } } - } - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 1c6ca8b92..1be97a4f8 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -64,22 +64,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // Horizontal convolution - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowAction(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); + new RowIntervalAction(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); // Vertical convolution - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowAction(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); + new RowIntervalAction(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); } /// /// A implementing the convolution logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, @@ -107,51 +107,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + public void Invoke(in RowInterval rows, Memory memory) { - int length = span.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(span); + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); int maxY = this.bounds.Bottom - 1; int maxX = this.bounds.Right - 1; - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span); - - if (this.preserveAlpha) + for (int y = rows.Min; y < rows.Max; y++) { - for (int x = 0; x < this.bounds.Width; x++) + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + + if (this.preserveAlpha) { - DenseMatrixUtils.Convolve3( - in this.kernel, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - maxY, - this.bounds.X, - maxX); + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } } - } - else - { - for (int x = 0; x < this.bounds.Width; x++) + else { - DenseMatrixUtils.Convolve4( - in this.kernel, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - maxY, - this.bounds.X, - maxX); + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } } - } - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 6a2acf770..b68dc56e0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -57,10 +57,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowAction(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha)); + new RowIntervalAction(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha)); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the convolution logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly int maxY; @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, @@ -100,48 +100,52 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + public void Invoke(in RowInterval rows, Memory memory) { - int length = span.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(span); + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), span); - - if (this.preserveAlpha) + for (int y = rows.Min; y < rows.Max; y++) { - for (int x = 0; x < this.bounds.Width; x++) + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + + if (this.preserveAlpha) { - DenseMatrixUtils.Convolve3( - in this.kernel, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } } - } - else - { - for (int x = 0; x < this.bounds.Width; x++) + else { - DenseMatrixUtils.Convolve4( - in this.kernel, - this.sourcePixels, - ref vectorSpanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref vectorSpanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } } - } - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 85736cbd9..e2480957e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -105,14 +105,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution ParallelRowIterator.IterateRows( Rectangle.FromLTRB(minX, minY, maxX, maxY), this.Configuration, - new RowAction(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX)); + new RowIntervalAction(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX)); } } /// /// A implementing the convolution logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Buffer2D targetPixels; private readonly Buffer2D passPixels; @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int shiftX; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( Buffer2D targetPixels, Buffer2D passPixels, int minX, @@ -140,26 +140,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - int offsetY = y - this.shiftY; + for (int y = rows.Min; y < rows.Max; y++) + { + int offsetY = y - this.shiftY; - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(offsetY)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(offsetY)); + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(offsetY)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(offsetY)); - for (int x = this.minX; x < this.maxX; x++) - { - int offsetX = x - this.shiftX; + for (int x = this.minX; x < this.maxX; x++) + { + int offsetX = x - this.shiftX; - // Grab the max components of the two pixels - ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); - ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); + // Grab the max components of the two pixels + ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); + ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); - var pixelValue = Vector4.Max( - currentPassPixel.ToVector4(), - currentTargetPixel.ToVector4()); + var pixelValue = Vector4.Max( + currentPassPixel.ToVector4(), + currentTargetPixel.ToVector4()); - currentTargetPixel.FromVector4(pixelValue); + currentTargetPixel.FromVector4(pixelValue); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index a12bcb1fd..2a181174c 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Drawing @@ -101,13 +102,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing ParallelRowIterator.IterateRows( workingRect, configuration, - new RowAction(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity)); + new RowIntervalAction(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity)); } /// /// A implementing the draw logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly ImageFrame sourceFrame; private readonly Image targetImage; @@ -120,7 +121,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing private readonly float opacity; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( ImageFrame sourceFrame, Image targetImage, PixelBlender blender, @@ -144,11 +145,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); - Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width); - this.blender.Blend(this.configuration, background, background, foreground, this.opacity); + for (int y = rows.Min; y < rows.Max; y++) + { + Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); + Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width); + this.blender.Blend(this.configuration, background, background, foreground, this.opacity); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 45f221c93..4abaf7ac4 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects ParallelRowIterator.IterateRows( this.SourceRectangle, this.Configuration, - new RowAction(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels)); + new RowIntervalAction(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels)); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// /// A implementing the convolution logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly int levels; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( Rectangle bounds, Buffer2D targetPixels, ImageFrame source, @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { int maxY = this.bounds.Bottom - 1; int maxX = this.bounds.Right - 1; @@ -117,66 +117,69 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects ref float blueBinRef = ref Unsafe.Add(ref redBinRef, this.levels); ref float greenBinRef = ref Unsafe.Add(ref blueBinRef, this.levels); - Span sourceRowPixelSpan = this.source.GetPixelRowSpan(y); - Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); - - PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); - - for (int x = this.bounds.X; x < this.bounds.Right; x++) + for (int y = rows.Min; y < rows.Max; y++) { - int maxIntensity = 0; - int maxIndex = 0; + 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 <= this.radius; fy++) + for (int x = this.bounds.X; x < this.bounds.Right; x++) { - int fyr = fy - this.radius; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); + int maxIntensity = 0; + int maxIndex = 0; - Span sourceOffsetRow = this.source.GetPixelRowSpan(offsetY); + // Clear the current shared buffer before processing each target pixel + bins.Memory.Span.Clear(); - for (int fx = 0; fx <= this.radius; fx++) + for (int fy = 0; fy <= this.radius; fy++) { - int fxr = fx - this.radius; - int offsetX = x + fxr; - offsetX = offsetX.Clamp(0, maxX); + int fyr = fy - this.radius; + int offsetY = y + fyr; - var vector = sourceOffsetRow[offsetX].ToVector4(); + offsetY = offsetY.Clamp(0, maxY); - float sourceRed = vector.X; - float sourceBlue = vector.Z; - float sourceGreen = vector.Y; + Span sourceOffsetRow = this.source.GetPixelRowSpan(offsetY); - int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1)); + for (int fx = 0; fx <= this.radius; fx++) + { + int fxr = fx - this.radius; + int offsetX = x + fxr; + offsetX = offsetX.Clamp(0, maxX); - Unsafe.Add(ref intensityBinRef, currentIntensity)++; - Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; - Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; - Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; + var vector = sourceOffsetRow[offsetX].ToVector4(); - if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) - { - maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); - maxIndex = currentIntensity; + float sourceRed = vector.X; + float sourceBlue = vector.Z; + float sourceGreen = vector.Y; + + int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1)); + + Unsafe.Add(ref intensityBinRef, currentIntensity)++; + Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; + Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; + Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; + + if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) + { + maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); + maxIndex = currentIntensity; + } } - } - 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; + 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; - targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); + targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); + } } - } - Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index c0f479756..fd725d3ba 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -50,16 +51,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowAction(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate)); + new RowIntervalAction(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate)); } /// /// A implementing the convolution logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly int startX; private readonly ImageFrame source; @@ -68,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly TDelegate rowProcessor; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( int startX, ImageFrame source, Configuration configuration, @@ -84,16 +85,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + public void Invoke(in RowInterval rows, Memory memory) { - int length = span.Length; - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers); + for (int y = rows.Min; y < rows.Max; y++) + { + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, vectorSpan, this.modifiers); - // Run the user defined pixel shader to the current row of pixels - Unsafe.AsRef(this.rowProcessor).Invoke(span, new Point(this.startX, y)); + // Run the user defined pixel shader to the current row of pixels + Unsafe.AsRef(this.rowProcessor).Invoke(vectorSpan, new Point(this.startX, y)); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, this.modifiers); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan, this.modifiers); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index c797c1358..cdb67e48b 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Filters @@ -36,16 +37,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowAction(interest.X, source, this.definition.Matrix, this.Configuration)); + new RowIntervalAction(interest.X, source, this.definition.Matrix, this.Configuration)); } /// /// A implementing the convolution logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly int startX; private readonly ImageFrame source; @@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( int startX, ImageFrame source, ColorMatrix matrix, @@ -67,15 +68,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + public void Invoke(in RowInterval rows, Memory memory) { - int length = span.Length; - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span); + for (int y = rows.Min; y < rows.Max; y++) + { + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, vectorSpan); - Vector4Utils.Transform(span, ref Unsafe.AsRef(this.matrix)); + Vector4Utils.Transform(vectorSpan, ref Unsafe.AsRef(this.matrix)); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index a4a643425..5d25bae82 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization ParallelRowIterator.IterateRows( workingRect, this.Configuration, - new GrayscaleLevelsRowAction(workingRect, histogramBuffer, source, this.LuminanceLevels)); + new GrayscaleLevelsRowIntervalAction(workingRect, histogramBuffer, source, this.LuminanceLevels)); Span histogram = histogramBuffer.GetSpan(); if (this.ClipHistogramEnabled) @@ -77,13 +77,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization ParallelRowIterator.IterateRows( workingRect, this.Configuration, - new CdfApplicationRowAction(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); + new CdfApplicationRowIntervalAction(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); } /// /// A implementing the grayscale levels logic for . /// - private readonly struct GrayscaleLevelsRowAction : IRowAction + private readonly struct GrayscaleLevelsRowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly IMemoryOwner histogramBuffer; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly int luminanceLevels; [MethodImpl(InliningOptions.ShortMethod)] - public GrayscaleLevelsRowAction( + public GrayscaleLevelsRowIntervalAction( in Rectangle bounds, IMemoryOwner histogramBuffer, ImageFrame source, @@ -105,15 +105,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); - ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); - - for (int x = 0; x < this.bounds.Width; x++) + for (int y = rows.Min; y < rows.Max; y++) { - int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels); - Unsafe.Add(ref histogramBase, luminance)++; + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) + { + int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; + } } } } @@ -121,7 +124,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// A implementing the cdf application levels logic for . /// - private readonly struct CdfApplicationRowAction : IRowAction + private readonly struct CdfApplicationRowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly IMemoryOwner cdfBuffer; @@ -130,7 +133,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly float numberOfPixelsMinusCdfMin; [MethodImpl(InliningOptions.ShortMethod)] - public CdfApplicationRowAction( + public CdfApplicationRowIntervalAction( in Rectangle bounds, IMemoryOwner cdfBuffer, ImageFrame source, @@ -146,17 +149,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); - ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); - - for (int x = 0; x < this.bounds.Width; x++) + for (int y = rows.Min; y < rows.Max; y++) { - ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); - int luminance = GetLuminance(pixel, this.luminanceLevels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin; - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); + int luminance = GetLuminance(pixel, this.luminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin; + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index 2c17d71c6..a9b91e837 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -52,10 +52,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays ParallelRowIterator.IterateRows( interest, configuration, - new RowAction(configuration, interest, blender, amount, colors, source)); + new RowIntervalAction(configuration, interest, blender, amount, colors, source)); } - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( Configuration configuration, Rectangle bounds, PixelBlender blender, @@ -82,18 +82,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + for (int y = rows.Min; y < rows.Max; y++) + { + Span destination = + this.source.GetPixelRowSpan(y) + .Slice(this.bounds.X, this.bounds.Width); - // Switch color & destination in the 2nd and 3rd places because we are - // applying the target color under the current one. - this.blender.Blend( - this.configuration, - destination, - this.colors.GetSpan(), - destination, - this.amount.GetSpan()); + // Switch color & destination in the 2nd and 3rd places because we are + // applying the target color under the current one. + this.blender.Blend( + this.configuration, + destination, + this.colors.GetSpan(), + destination, + this.amount.GetSpan()); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 6777e3234..65a87fbf0 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -55,13 +55,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(glowColor); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, configuration, - new RowAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); + new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); } - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( Configuration configuration, Rectangle bounds, IMemoryOwner colors, @@ -94,24 +94,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + public void Invoke(in RowInterval rows, Memory memory) { + Span amountsSpan = memory.Span; Span colorSpan = this.colors.GetSpan(); - for (int i = 0; i < this.bounds.Width; i++) + for (int y = rows.Min; y < rows.Max; y++) { - float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); - span[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); - } + for (int i = 0; i < this.bounds.Width; i++) + { + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + amountsSpan[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); + } - Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - this.blender.Blend( - this.configuration, - destination, - destination, - colorSpan, - span); + this.blender.Blend( + this.configuration, + destination, + destination, + colorSpan, + amountsSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index 6e2c3c442..11887433c 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -63,13 +63,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(vignetteColor); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, configuration, - new RowAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); + new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); } - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( Configuration configuration, Rectangle bounds, IMemoryOwner colors, @@ -102,24 +102,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + public void Invoke(in RowInterval rows, Memory memory) { + Span amountsSpan = memory.Span; Span colorSpan = this.colors.GetSpan(); - for (int i = 0; i < this.bounds.Width; i++) + for (int y = rows.Min; y < rows.Max; y++) { - float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); - span[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); + for (int i = 0; i < this.bounds.Width; i++) + { + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + amountsSpan[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); + } + + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + this.blender.Blend( + this.configuration, + destination, + destination, + colorSpan, + amountsSpan); } - - Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - - this.blender.Blend( - this.configuration, - destination, - destination, - colorSpan, - span); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 447a99eec..402a05249 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -60,23 +61,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( targetBounds, configuration, - new NearestNeighborRowAction(this.SourceRectangle, ref matrix, width, source, destination)); + new NearestNeighborRowIntervalAction(this.SourceRectangle, ref matrix, width, source, destination)); return; } using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( targetBounds, configuration, - new RowAction(configuration, kernelMap, ref matrix, width, source, destination)); + new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination)); } /// /// A implementing the nearest neighbor resampler logic for . /// - private readonly struct NearestNeighborRowAction : IRowAction + private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly Matrix3x2 matrix; @@ -85,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public NearestNeighborRowAction( + public NearestNeighborRowIntervalAction( Rectangle bounds, ref Matrix3x2 matrix, int maxX, @@ -100,17 +101,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } /// + /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - Span destRow = this.destination.GetPixelRowSpan(y); - - for (int x = 0; x < this.maxX; x++) + for (int y = rows.Min; y < rows.Max; y++) { - var point = Point.Transform(new Point(x, y), this.matrix); - if (this.bounds.Contains(point.X, point.Y)) + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) { - destRow[x] = this.source[point.X, point.Y]; + var point = Point.Transform(new Point(x, y), this.matrix); + if (this.bounds.Contains(point.X, point.Y)) + { + destRow[x] = this.source[point.X, point.Y]; + } } } } @@ -119,7 +124,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// A implementing the transformation logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Configuration configuration; private readonly TransformKernelMap kernelMap; @@ -129,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( Configuration configuration, TransformKernelMap kernelMap, ref Matrix3x2 matrix, @@ -147,31 +152,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + public void Invoke(in RowInterval rows, Memory memory) { - Span targetRowSpan = this.destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, span); - ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); - ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); - - for (int x = 0; x < this.maxX; x++) + Span vectorSpan = memory.Span; + for (int y = rows.Min; y < rows.Max; y++) { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - var point = Vector2.Transform(new Vector2(x, y), this.matrix); - this.kernelMap.Convolve( - point, - x, - ref ySpanRef, - ref xSpanRef, - this.source.PixelBuffer, - span); - } + Span targetRowSpan = this.destination.GetPixelRowSpan(y); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, vectorSpan); + ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); + ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - span, - targetRowSpan); + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var point = Vector2.Transform(new Vector2(x, y), this.matrix); + this.kernelMap.Convolve( + point, + x, + ref ySpanRef, + ref xSpanRef, + this.source.PixelBuffer, + vectorSpan); + } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + vectorSpan, + targetRowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index b294ef465..103c5d3ff 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -47,25 +48,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle bounds = this.cropRectangle; // Copying is cheap, we should process more pixels per task: - ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); + ParallelExecutionSettings parallelSettings = + ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); + + var rowAction = new RowIntervalAction(ref bounds, source, destination); ParallelRowIterator.IterateRows( bounds, in parallelSettings, - new RowAction(ref bounds, source, destination)); + in rowAction); } /// /// A implementing the processor logic for . /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly ImageFrame source; private readonly ImageFrame destination; + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The source for the current instance. + /// The destination for the current instance. [MethodImpl(InliningOptions.ShortMethod)] - public RowAction(ref Rectangle bounds, ImageFrame source, ImageFrame destination) + public RowIntervalAction(ref Rectangle bounds, ImageFrame source, ImageFrame destination) { this.bounds = bounds; this.source = source; @@ -74,11 +84,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); - Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); - sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); + Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); + sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs index 91cb47891..041f602a5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -78,23 +79,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( source.Bounds(), configuration, - new RowAction(source)); + new RowIntervalAction(source)); } - /// - /// A implementing the reverse logic for . - /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction(ImageFrame source) => this.source = source; + public RowIntervalAction(ImageFrame source) => this.source = source; [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - this.source.GetPixelRowSpan(y).Reverse(); + for (int y = rows.Min; y < rows.Max; y++) + { + this.source.GetPixelRowSpan(y).Reverse(); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 5989f2389..5034b072f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -16,9 +17,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class ProjectiveTransformProcessor : TransformProcessor where TPixel : struct, IPixel { - private readonly Size targetSize; + private Size targetSize; private readonly IResampler resampler; - private readonly Matrix4x4 transformMatrix; + private Matrix4x4 transformMatrix; /// /// Initializes a new instance of the class. @@ -62,23 +63,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( targetBounds, configuration, - new NearestNeighborRowAction(ref sourceBounds, ref matrix, width, source, destination)); + new NearestNeighborRowIntervalAction(ref sourceBounds, ref matrix, width, source, destination)); return; } using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( targetBounds, configuration, - new RowAction(configuration, kernelMap, ref matrix, width, source, destination)); + new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination)); } - /// - /// A implementing the nearest neighbor interpolation logic for . - /// - private readonly struct NearestNeighborRowAction : IRowAction + private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly Matrix4x4 matrix; @@ -87,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public NearestNeighborRowAction( + public NearestNeighborRowIntervalAction( ref Rectangle bounds, ref Matrix4x4 matrix, int maxX, @@ -101,30 +99,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.destination = destination; } - /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - Span destRow = this.destination.GetPixelRowSpan(y); - - for (int x = 0; x < this.maxX; x++) + for (int y = rows.Min; y < rows.Max; y++) { - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); - int px = (int)MathF.Round(point.X); - int py = (int)MathF.Round(point.Y); + Span destRow = this.destination.GetPixelRowSpan(y); - if (this.bounds.Contains(px, py)) + for (int x = 0; x < this.maxX; x++) { - destRow[x] = this.source[px, py]; + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); + + if (this.bounds.Contains(px, py)) + { + destRow[x] = this.source[px, py]; + } } } } } - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Configuration configuration; private readonly TransformKernelMap kernelMap; @@ -134,7 +131,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( Configuration configuration, TransformKernelMap kernelMap, ref Matrix4x4 matrix, @@ -150,33 +147,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.destination = destination; } - /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + public void Invoke(in RowInterval rows, Memory memory) { - Span targetRowSpan = this.destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, span); - ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); - ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); - - for (int x = 0; x < this.maxX; x++) + Span vectorSpan = memory.Span; + for (int y = rows.Min; y < rows.Max; y++) { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); - this.kernelMap.Convolve( - point, - x, - ref ySpanRef, - ref xSpanRef, - this.source.PixelBuffer, - span); - } + Span targetRowSpan = this.destination.GetPixelRowSpan(y); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, vectorSpan); + ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); + ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - span, - targetRowSpan); + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); + this.kernelMap.Convolve( + point, + x, + ref ySpanRef, + ref xSpanRef, + this.source.PixelBuffer, + vectorSpan); + } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + vectorSpan, + targetRowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 3eeda1781..02622622d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( interest, configuration, - new RowAction(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination)); + new RowIntervalAction(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination)); return; } @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms base.Dispose(disposing); } - private readonly struct RowAction : IRowAction + private readonly struct RowIntervalAction : IRowIntervalAction { private readonly Rectangle sourceBounds; private readonly Rectangle destinationBounds; @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowIntervalAction( Rectangle sourceBounds, Rectangle destinationBounds, float widthFactor, @@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { int sourceX = this.sourceBounds.X; int sourceY = this.sourceBounds.Y; @@ -183,14 +183,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int destLeft = this.destinationBounds.Left; int destRight = this.destinationBounds.Right; - // Y coordinates of source points - Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); - Span targetRow = this.destination.GetPixelRowSpan(y); - - for (int x = destLeft; x < destRight; x++) + for (int y = rows.Min; y < rows.Max; y++) { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; + // Y coordinates of source points + Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.GetPixelRowSpan(y); + + for (int x = destLeft; x < destRight; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs index d20954d0f..bf03ce319 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( source.Bounds(), configuration, - new Rotate180RowAction(source.Width, source.Height, source, destination)); + new Rotate180RowIntervalAction(source.Width, source.Height, source, destination)); } /// @@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( source.Bounds(), configuration, - new Rotate270RowAction(destination.Bounds(), source.Width, source.Height, source, destination)); + new Rotate270RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination)); } /// @@ -162,10 +162,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( source.Bounds(), configuration, - new Rotate90RowAction(destination.Bounds(), source.Width, source.Height, source, destination)); + new Rotate90RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination)); } - private readonly struct Rotate180RowAction : IRowAction + private readonly struct Rotate180RowIntervalAction : IRowIntervalAction { private readonly int width; private readonly int height; @@ -173,7 +173,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public Rotate180RowAction( + public Rotate180RowIntervalAction( int width, int height, ImageFrame source, @@ -186,19 +186,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - Span sourceRow = this.source.GetPixelRowSpan(y); - Span targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); - - for (int x = 0; x < this.width; x++) + for (int y = rows.Min; y < rows.Max; y++) { - targetRow[this.width - x - 1] = sourceRow[x]; + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); + + for (int x = 0; x < this.width; x++) + { + targetRow[this.width - x - 1] = sourceRow[x]; + } } } } - private readonly struct Rotate270RowAction : IRowAction + private readonly struct Rotate270RowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly int width; @@ -207,7 +210,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public Rotate270RowAction( + public Rotate270RowIntervalAction( Rectangle bounds, int width, int height, @@ -222,24 +225,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - Span sourceRow = this.source.GetPixelRowSpan(y); - for (int x = 0; x < this.width; x++) + for (int y = rows.Min; y < rows.Max; y++) { - int newX = this.height - y - 1; - newX = this.height - newX - 1; - int newY = this.width - x - 1; - - if (this.bounds.Contains(newX, newY)) + Span sourceRow = this.source.GetPixelRowSpan(y); + for (int x = 0; x < this.width; x++) { - this.destination[newX, newY] = sourceRow[x]; + int newX = this.height - y - 1; + newX = this.height - newX - 1; + int newY = this.width - x - 1; + + if (this.bounds.Contains(newX, newY)) + { + this.destination[newX, newY] = sourceRow[x]; + } } } } } - private readonly struct Rotate90RowAction : IRowAction + private readonly struct Rotate90RowIntervalAction : IRowIntervalAction { private readonly Rectangle bounds; private readonly int width; @@ -248,7 +254,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public Rotate90RowAction( + public Rotate90RowIntervalAction( Rectangle bounds, int width, int height, @@ -263,15 +269,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(in RowInterval rows) { - Span sourceRow = this.source.GetPixelRowSpan(y); - int newX = this.height - y - 1; - for (int x = 0; x < this.width; x++) + for (int y = rows.Min; y < rows.Max; y++) { - if (this.bounds.Contains(newX, x)) + Span sourceRow = this.source.GetPixelRowSpan(y); + int newX = this.height - y - 1; + for (int x = 0; x < this.width; x++) { - this.destination[newX, x] = sourceRow[x]; + if (this.bounds.Contains(newX, x)) + { + this.destination[newX, x] = sourceRow[x]; + } } } } From 89e3898e9700a89bdbc17ffc3ab7db69520c1349 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 8 Feb 2020 22:15:18 +1100 Subject: [PATCH 46/58] Update PixelateProcessor{TPixel}.cs --- .../Processing/Processors/Effects/PixelateProcessor{TPixel}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs index 506cea8da..e541b0d68 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects new RowAction(interest, size, source).Invoke); } - private readonly struct RowAction : IRowAction + private readonly struct RowAction { private readonly int minX; private readonly int maxX; From 2b00433350136eb846b2cd50130619d824714207 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 9 Feb 2020 00:09:45 +1100 Subject: [PATCH 47/58] Update AdaptiveHistogramEqualizationProcessor and rename interfaces --- ...rvalAction.cs => IRowIntervalOperation.cs} | 18 +- ...}.cs => IRowIntervalOperation{TBuffer}.cs} | 20 +- .../Advanced/ParallelRowIterator.cs | 52 +-- src/ImageSharp/ImageFrame{TPixel}.cs | 6 +- .../BinaryThresholdProcessor{TPixel}.cs | 6 +- .../Convolution/BokehBlurProcessor{TPixel}.cs | 26 +- .../Convolution2DProcessor{TPixel}.cs | 8 +- .../Convolution2PassProcessor{TPixel}.cs | 12 +- .../ConvolutionProcessor{TPixel}.cs | 8 +- .../EdgeDetectorCompassProcessor{TPixel}.cs | 6 +- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 6 +- .../Effects/OilPaintingProcessor{TPixel}.cs | 6 +- ...lRowDelegateProcessor{TPixel,TDelegate}.cs | 8 +- .../Effects/PixelateProcessor{TPixel}.cs | 11 +- .../Filters/FilterProcessor{TPixel}.cs | 8 +- ...eHistogramEqualizationProcessor{TPixel}.cs | 299 ++++++++++++------ ...lHistogramEqualizationProcessor{TPixel}.cs | 12 +- .../BackgroundColorProcessor{TPixel}.cs | 6 +- .../Overlays/GlowProcessor{TPixel}.cs | 8 +- .../Overlays/VignetteProcessor{TPixel}.cs | 8 +- .../AffineTransformProcessor{TPixel}.cs | 14 +- .../Transforms/CropProcessor{TPixel}.cs | 8 +- .../Transforms/FlipProcessor{TPixel}.cs | 6 +- .../ProjectiveTransformProcessor{TPixel}.cs | 14 +- .../Resize/ResizeProcessor{TPixel}.cs | 6 +- .../Transforms/RotateProcessor{TPixel}.cs | 18 +- 26 files changed, 347 insertions(+), 253 deletions(-) rename src/ImageSharp/Advanced/{IRowIntervalAction.cs => IRowIntervalOperation.cs} (81%) rename src/ImageSharp/Advanced/{IRowIntervalAction{TBuffer}.cs => IRowIntervalOperation{TBuffer}.cs} (83%) diff --git a/src/ImageSharp/Advanced/IRowIntervalAction.cs b/src/ImageSharp/Advanced/IRowIntervalOperation.cs similarity index 81% rename from src/ImageSharp/Advanced/IRowIntervalAction.cs rename to src/ImageSharp/Advanced/IRowIntervalOperation.cs index a24cda320..9aa79e730 100644 --- a/src/ImageSharp/Advanced/IRowIntervalAction.cs +++ b/src/ImageSharp/Advanced/IRowIntervalOperation.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Advanced /// /// Defines the contract for an action that operates on a row interval. /// - public interface IRowIntervalAction + public interface IRowIntervalOperation { /// /// Invokes the method passing the row interval. @@ -40,13 +40,13 @@ namespace SixLabors.ImageSharp.Advanced } } - internal readonly struct WrappingRowIntervalAction + internal readonly struct WrappingRowIntervalOperation { private readonly WrappingRowIntervalInfo info; private readonly Action action; [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowIntervalAction(in WrappingRowIntervalInfo info, Action action) + public WrappingRowIntervalOperation(in WrappingRowIntervalInfo info, Action action) { this.info = info; this.action = action; @@ -69,17 +69,17 @@ namespace SixLabors.ImageSharp.Advanced } } - internal readonly struct WrappingRowIntervalAction - where T : struct, IRowIntervalAction + internal readonly struct WrappingRowIntervalOperation + where T : struct, IRowIntervalOperation { private readonly WrappingRowIntervalInfo info; - private readonly T action; + private readonly T operation; [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowIntervalAction(in WrappingRowIntervalInfo info, in T action) + public WrappingRowIntervalOperation(in WrappingRowIntervalInfo info, in T operation) { this.info = info; - this.action = action; + this.operation = operation; } [MethodImpl(InliningOptions.ShortMethod)] @@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.Advanced var rows = new RowInterval(yMin, yMax); // Skip the safety copy when invoking a potentially impure method on a readonly field - Unsafe.AsRef(this.action).Invoke(in rows); + Unsafe.AsRef(this.operation).Invoke(in rows); } } } diff --git a/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs similarity index 83% rename from src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs rename to src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs index 89ef2ab44..18ebc9fb9 100644 --- a/src/ImageSharp/Advanced/IRowIntervalAction{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Advanced /// Defines the contract for an action that operates on a row interval with a temporary buffer. /// /// The type of buffer elements. - public interface IRowIntervalAction + public interface IRowIntervalOperation where TBuffer : unmanaged { /// @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Advanced void Invoke(in RowInterval rows, Memory memory); } - internal readonly struct WrappingRowIntervalBufferAction + internal readonly struct WrappingRowIntervalBufferOperation where TBuffer : unmanaged { private readonly WrappingRowIntervalInfo info; @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Advanced private readonly Action> action; [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowIntervalBufferAction( + public WrappingRowIntervalBufferOperation( in WrappingRowIntervalInfo info, MemoryAllocator allocator, Action> action) @@ -59,23 +59,23 @@ namespace SixLabors.ImageSharp.Advanced } } - internal readonly struct WrappingRowIntervalBufferAction - where T : struct, IRowIntervalAction + internal readonly struct WrappingRowIntervalBufferOperation + where T : struct, IRowIntervalOperation where TBuffer : unmanaged { private readonly WrappingRowIntervalInfo info; private readonly MemoryAllocator allocator; - private readonly T action; + private readonly T operation; [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowIntervalBufferAction( + public WrappingRowIntervalBufferOperation( in WrappingRowIntervalInfo info, MemoryAllocator allocator, - in T action) + in T operation) { this.info = info; this.allocator = allocator; - this.action = action; + this.operation = operation; } [MethodImpl(InliningOptions.ShortMethod)] @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Advanced using IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX); - Unsafe.AsRef(this.action).Invoke(in rows, buffer.Memory); + Unsafe.AsRef(this.operation).Invoke(in rows, buffer.Memory); } } } diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 5c8a30f68..beef99c1c 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -5,7 +5,6 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Threading.Tasks; - using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Advanced @@ -21,13 +20,13 @@ namespace SixLabors.ImageSharp.Advanced /// /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// - /// The type of row action to perform. + /// The type of row operation to perform. /// The . /// The to get the parallel settings from. /// The method body defining the iteration logic on a single . [MethodImpl(InliningOptions.ShortMethod)] public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) - where T : struct, IRowIntervalAction + where T : struct, IRowIntervalOperation { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); IterateRows(rectangle, in parallelSettings, in body); @@ -36,15 +35,15 @@ namespace SixLabors.ImageSharp.Advanced /// /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// - /// The type of row action to perform. + /// The type of row operation to perform. /// The . /// The . - /// The method body defining the iteration logic on a single . + /// The method body defining the iteration logic on a single . public static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, - in T body) - where T : struct, IRowIntervalAction + in T operation) + where T : struct, IRowIntervalOperation { ValidateRectangle(rectangle); @@ -60,14 +59,14 @@ namespace SixLabors.ImageSharp.Advanced if (numOfSteps == 1) { var rows = new RowInterval(top, bottom); - Unsafe.AsRef(body).Invoke(in rows); + Unsafe.AsRef(operation).Invoke(in rows); return; } int verticalStep = DivideCeil(rectangle.Height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); - var rowAction = new WrappingRowIntervalAction(in rowInfo, in body); + var rowAction = new WrappingRowIntervalOperation(in rowInfo, in operation); Parallel.For( 0, @@ -78,30 +77,35 @@ namespace SixLabors.ImageSharp.Advanced /// /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. + /// instantiating a temporary buffer for each invocation. /// - /// The type of row action to perform. + /// The type of row operation to perform. /// The type of buffer elements. /// The . /// The to get the parallel settings from. - /// The method body defining the iteration logic on a single . - public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) - where T : struct, IRowIntervalAction + /// The method body defining the iteration logic on a single . + public static void IterateRows(Rectangle rectangle, Configuration configuration, in T operation) + where T : struct, IRowIntervalOperation where TBuffer : unmanaged { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, in body); + IterateRows(rectangle, in parallelSettings, in operation); } /// /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. + /// instantiating a temporary buffer for each invocation. /// - internal static void IterateRows( + /// The type of row operation to perform. + /// The type of buffer elements. + /// The . + /// The . + /// The method body defining the iteration logic on a single . + public static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, - in T body) - where T : struct, IRowIntervalAction + in T operation) + where T : struct, IRowIntervalOperation where TBuffer : unmanaged { ValidateRectangle(rectangle); @@ -121,7 +125,7 @@ namespace SixLabors.ImageSharp.Advanced var rows = new RowInterval(top, bottom); using (IMemoryOwner buffer = allocator.Allocate(width)) { - Unsafe.AsRef(body).Invoke(rows, buffer.Memory); + Unsafe.AsRef(operation).Invoke(rows, buffer.Memory); } return; @@ -130,13 +134,13 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); - var rowAction = new WrappingRowIntervalBufferAction(in rowInfo, allocator, in body); + var rowOperation = new WrappingRowIntervalBufferOperation(in rowInfo, allocator, in operation); Parallel.For( 0, numOfSteps, parallelOptions, - rowAction.Invoke); + rowOperation.Invoke); } /// @@ -183,7 +187,7 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(rectangle.Height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); - var rowAction = new WrappingRowIntervalAction(in rowInfo, body); + var rowAction = new WrappingRowIntervalOperation(in rowInfo, body); Parallel.For( 0, @@ -242,7 +246,7 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(rectangle.Height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); - var rowAction = new WrappingRowIntervalBufferAction(in rowInfo, allocator, body); + var rowAction = new WrappingRowIntervalBufferOperation(in rowInfo, allocator, body); Parallel.For( 0, diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index c3bab8e65..e3dbad505 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -263,7 +263,7 @@ namespace SixLabors.ImageSharp ParallelRowIterator.IterateRows( this.Bounds(), configuration, - new RowIntervalAction(this, target, configuration)); + new RowIntervalOperation(this, target, configuration)); return target; } @@ -289,7 +289,7 @@ namespace SixLabors.ImageSharp /// /// A implementing the clone logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation where TPixel2 : struct, IPixel { private readonly ImageFrame source; @@ -297,7 +297,7 @@ namespace SixLabors.ImageSharp private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( ImageFrame source, ImageFrame target, Configuration configuration) diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 9e9dccc46..129edbb03 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -54,13 +54,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization ParallelRowIterator.IterateRows( workingRect, configuration, - new RowIntervalAction(source, upper, lower, threshold, startX, endX, isAlphaOnly)); + new RowIntervalOperation(source, upper, lower, threshold, startX, endX, isAlphaOnly)); } /// /// A implementing the clone logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly ImageFrame source; private readonly TPixel upper; @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization private readonly bool isAlphaOnly; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( ImageFrame source, TPixel upper, TPixel lower, diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 834120f84..3fb62ed19 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -268,10 +268,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution protected override void OnFrameApply(ImageFrame source) { // Preliminary gamma highlight pass - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( this.SourceRectangle, this.Configuration, - new ApplyGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); + new ApplyGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); // Create a 0-filled buffer to use to store the result of the component convolutions using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean); @@ -285,7 +285,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution ParallelRowIterator.IterateRows( this.SourceRectangle, this.Configuration, - new ApplyInverseGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); + new ApplyInverseGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); } /// @@ -317,20 +317,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution ParallelRowIterator.IterateRows( sourceRectangle, configuration, - new ApplyVerticalConvolutionRowIntervalAction(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); + new ApplyVerticalConvolutionRowIntervalOperation(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer ParallelRowIterator.IterateRows( sourceRectangle, configuration, - new ApplyHorizontalConvolutionRowIntervalAction(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); + new ApplyHorizontalConvolutionRowIntervalOperation(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); } } /// /// A implementing the vertical convolution logic for . /// - private readonly struct ApplyVerticalConvolutionRowIntervalAction : IRowIntervalAction + private readonly struct ApplyVerticalConvolutionRowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly Buffer2D targetValues; @@ -340,7 +340,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int maxX; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyVerticalConvolutionRowIntervalAction( + public ApplyVerticalConvolutionRowIntervalOperation( ref Rectangle bounds, Buffer2D targetValues, Buffer2D sourcePixels, @@ -373,7 +373,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the horizontal convolution logic for . /// - private readonly struct ApplyHorizontalConvolutionRowIntervalAction : IRowIntervalAction + private readonly struct ApplyHorizontalConvolutionRowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly Buffer2D targetValues; @@ -385,7 +385,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int maxX; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyHorizontalConvolutionRowIntervalAction( + public ApplyHorizontalConvolutionRowIntervalOperation( ref Rectangle bounds, Buffer2D targetValues, Buffer2D sourceValues, @@ -422,7 +422,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the gamma exposure logic for . /// - private readonly struct ApplyGammaExposureRowIntervalAction : IRowIntervalAction + private readonly struct ApplyGammaExposureRowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -430,7 +430,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly float gamma; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyGammaExposureRowIntervalAction( + public ApplyGammaExposureRowIntervalOperation( Rectangle bounds, Buffer2D targetPixels, Configuration configuration, @@ -471,7 +471,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the inverse gamma exposure logic for . /// - private readonly struct ApplyInverseGammaExposureRowIntervalAction : IRowIntervalAction + private readonly struct ApplyInverseGammaExposureRowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -480,7 +480,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly float inverseGamma; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyInverseGammaExposureRowIntervalAction( + public ApplyInverseGammaExposureRowIntervalOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourceValues, diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index cd550a335..0340482fe 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -66,10 +66,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowIntervalAction(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha)); + new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha)); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly int maxY; @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 1be97a4f8..3cbbf8c46 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -64,22 +64,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // Horizontal convolution - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowIntervalAction(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); + new RowIntervalOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); // Vertical convolution - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowIntervalAction(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); + new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); } /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index b68dc56e0..2774a2f88 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -57,10 +57,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowIntervalAction(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha)); + new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha)); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly int maxY; @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index e2480957e..f390ee1ff 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -105,14 +105,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution ParallelRowIterator.IterateRows( Rectangle.FromLTRB(minX, minY, maxX, maxY), this.Configuration, - new RowIntervalAction(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX)); + new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX)); } } /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Buffer2D targetPixels; private readonly Buffer2D passPixels; @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int shiftX; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( Buffer2D targetPixels, Buffer2D passPixels, int minX, diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 2a181174c..88fa6bec3 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -102,13 +102,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing ParallelRowIterator.IterateRows( workingRect, configuration, - new RowIntervalAction(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity)); + new RowIntervalOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity)); } /// /// A implementing the draw logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly ImageFrame sourceFrame; private readonly Image targetImage; @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing private readonly float opacity; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( ImageFrame sourceFrame, Image targetImage, PixelBlender blender, diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 4abaf7ac4..1c3be4e63 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects ParallelRowIterator.IterateRows( this.SourceRectangle, this.Configuration, - new RowIntervalAction(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels)); + new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels)); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly int levels; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( Rectangle bounds, Buffer2D targetPixels, ImageFrame source, diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index fd725d3ba..3f8dcd8d9 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -51,16 +51,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowIntervalAction(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate)); + new RowIntervalOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate)); } /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly int startX; private readonly ImageFrame source; @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly TDelegate rowProcessor; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( int startX, ImageFrame source, Configuration configuration, diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs index e541b0d68..157ac7df1 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading.Tasks; - using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -29,9 +28,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// The source area to process for the current processor instance. public PixelateProcessor(Configuration configuration, PixelateProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) - { - this.definition = definition; - } + => this.definition = definition; private int Size => this.definition.Size; @@ -51,10 +48,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects Parallel.ForEach( range, this.Configuration.GetParallelOptions(), - new RowAction(interest, size, source).Invoke); + new RowOperation(interest, size, source).Invoke); } - private readonly struct RowAction + private readonly struct RowOperation { private readonly int minX; private readonly int maxX; @@ -66,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowAction( + public RowOperation( Rectangle bounds, int size, ImageFrame source) diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index cdb67e48b..159a1f981 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -37,16 +37,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, this.Configuration, - new RowIntervalAction(interest.X, source, this.definition.Matrix, this.Configuration)); + new RowIntervalOperation(interest.X, source, this.definition.Matrix, this.Configuration)); } /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly int startX; private readonly ImageFrame source; @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( int startX, ImageFrame source, ColorMatrix matrix, diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index b5b8cfe56..64aaa884b 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -7,8 +7,7 @@ using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; - +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -81,56 +80,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization yStart += tileHeight; } - Parallel.For( - 0, - tileYStartPositions.Count, - new ParallelOptions { MaxDegreeOfParallelism = this.Configuration.MaxDegreeOfParallelism }, - index => - { - int y = tileYStartPositions[index].y; - int cdfYY = tileYStartPositions[index].cdfY; - - // It's unfortunate that we have to do this per iteration. - ref TPixel sourceBase = ref source.GetPixelReference(0, 0); - - int cdfX = 0; - int x = halfTileWidth; - for (int tile = 0; tile < tileCount - 1; tile++) - { - int tileY = 0; - int yEnd = Math.Min(y + tileHeight, sourceHeight); - int xEnd = Math.Min(x + tileWidth, sourceWidth); - for (int dy = y; dy < yEnd; dy++) - { - int dyOffSet = dy * sourceWidth; - int tileX = 0; - for (int dx = x; dx < xEnd; dx++) - { - ref TPixel pixel = ref Unsafe.Add(ref sourceBase, dyOffSet + dx); - float luminanceEqualized = InterpolateBetweenFourTiles( - pixel, - cdfData, - tileCount, - tileCount, - tileX, - tileY, - cdfX, - cdfYY, - tileWidth, - tileHeight, - luminanceLevels); - - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - tileX++; - } - - tileY++; - } - - cdfX++; - x += tileWidth; - } - }); + ParallelRowIterator.IterateRows( + new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), + this.Configuration, + new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source)); ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); @@ -416,6 +369,95 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private static float LinearInterpolation(float left, float right, float t) => left + ((right - left) * t); + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly CdfTileData cdfData; + private readonly List<(int y, int cdfY)> tileYStartPositions; + private readonly int tileWidth; + private readonly int tileHeight; + private readonly int tileCount; + private readonly int halfTileWidth; + private readonly int luminanceLevels; + private readonly ImageFrame source; + private readonly int sourceWidth; + private readonly int sourceHeight; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + CdfTileData cdfData, + List<(int y, int cdfY)> tileYStartPositions, + int tileWidth, + int tileHeight, + int tileCount, + int halfTileWidth, + int luminanceLevels, + ImageFrame source) + { + this.cdfData = cdfData; + this.tileYStartPositions = tileYStartPositions; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.tileCount = tileCount; + this.halfTileWidth = halfTileWidth; + this.luminanceLevels = luminanceLevels; + this.source = source; + this.sourceWidth = source.Width; + this.sourceHeight = source.Height; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ref TPixel sourceBase = ref this.source.GetPixelReference(0, 0); + + for (int index = rows.Min; index < rows.Max; index++) + { + (int y, int cdfY) tileYStartPosition = this.tileYStartPositions[index]; + int y = tileYStartPosition.y; + int cdfYY = tileYStartPosition.cdfY; + + int cdfX = 0; + int x = this.halfTileWidth; + for (int tile = 0; tile < this.tileCount - 1; tile++) + { + int tileY = 0; + int yEnd = Math.Min(y + this.tileHeight, this.sourceHeight); + int xEnd = Math.Min(x + this.tileWidth, this.sourceWidth); + for (int dy = y; dy < yEnd; dy++) + { + int dyOffSet = dy * this.sourceWidth; + int tileX = 0; + for (int dx = x; dx < xEnd; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref sourceBase, dyOffSet + dx); + float luminanceEqualized = InterpolateBetweenFourTiles( + pixel, + this.cdfData, + this.tileCount, + this.tileCount, + tileX, + tileY, + cdfX, + cdfYY, + this.tileWidth, + this.tileHeight, + this.luminanceLevels); + + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + tileX++; + } + + tileY++; + } + + cdfX++; + x += this.tileWidth; + } + } + } + } + /// /// Contains the results of the cumulative distribution function for all tiles. /// @@ -470,57 +512,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public void CalculateLookupTables(ImageFrame source, HistogramEqualizationProcessor processor) { - int sourceWidth = this.sourceWidth; - int sourceHeight = this.sourceHeight; - int tileWidth = this.tileWidth; - int tileHeight = this.tileHeight; - int luminanceLevels = this.luminanceLevels; - - Parallel.For( - 0, - this.tileYStartPositions.Count, - new ParallelOptions { MaxDegreeOfParallelism = this.configuration.MaxDegreeOfParallelism }, - index => - { - int cdfX = 0; - int cdfY = this.tileYStartPositions[index].cdfY; - int y = this.tileYStartPositions[index].y; - int endY = Math.Min(y + tileHeight, sourceHeight); - ref TPixel sourceBase = ref source.GetPixelReference(0, 0); - ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY)); - - using (IMemoryOwner histogramBuffer = this.memoryAllocator.Allocate(luminanceLevels)) - { - Span histogram = histogramBuffer.GetSpan(); - ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - - for (int x = 0; x < sourceWidth; x += tileWidth) - { - histogram.Clear(); - ref int cdfBase = ref MemoryMarshal.GetReference(this.GetCdfLutSpan(cdfX, index)); - - int xlimit = Math.Min(x + tileWidth, sourceWidth); - for (int dy = y; dy < endY; dy++) - { - int dyOffset = dy * sourceWidth; - for (int dx = x; dx < xlimit; dx++) - { - int luminance = GetLuminance(Unsafe.Add(ref sourceBase, dyOffset + dx), luminanceLevels); - histogram[luminance]++; - } - } - - if (processor.ClipHistogramEnabled) - { - processor.ClipHistogram(histogram, processor.ClipLimit); - } - - Unsafe.Add(ref cdfMinBase, cdfX) = processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); - - cdfX++; - } - } - }); + var rowOperation = new RowIntervalOperation( + processor, + this.memoryAllocator, + this.cdfMinBuffer2D, + this.cdfLutBuffer2D, + this.tileYStartPositions, + this.tileWidth, + this.tileHeight, + this.luminanceLevels, + source); + + ParallelRowIterator.IterateRows( + new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), + this.configuration, + in rowOperation); } [MethodImpl(InliningOptions.ShortMethod)] @@ -548,6 +554,93 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.cdfMinBuffer2D.Dispose(); this.cdfLutBuffer2D.Dispose(); } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly HistogramEqualizationProcessor processor; + private readonly MemoryAllocator allocator; + private readonly Buffer2D cdfMinBuffer2D; + private readonly Buffer2D cdfLutBuffer2D; + private readonly List<(int y, int cdfY)> tileYStartPositions; + private readonly int tileWidth; + private readonly int tileHeight; + private readonly int luminanceLevels; + private readonly ImageFrame source; + private readonly int sourceWidth; + private readonly int sourceHeight; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + HistogramEqualizationProcessor processor, + MemoryAllocator allocator, + Buffer2D cdfMinBuffer2D, + Buffer2D cdfLutBuffer2D, + List<(int y, int cdfY)> tileYStartPositions, + int tileWidth, + int tileHeight, + int luminanceLevels, + ImageFrame source) + { + this.processor = processor; + this.allocator = allocator; + this.cdfMinBuffer2D = cdfMinBuffer2D; + this.cdfLutBuffer2D = cdfLutBuffer2D; + this.tileYStartPositions = tileYStartPositions; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.luminanceLevels = luminanceLevels; + this.source = source; + this.sourceWidth = source.Width; + this.sourceHeight = source.Height; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ref TPixel sourceBase = ref this.source.GetPixelReference(0, 0); + + for (int index = rows.Min; index < rows.Max; index++) + { + int cdfX = 0; + int cdfY = this.tileYStartPositions[index].cdfY; + int y = this.tileYStartPositions[index].y; + int endY = Math.Min(y + this.tileHeight, this.sourceHeight); + ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY)); + + using IMemoryOwner histogramBuffer = this.allocator.Allocate(this.luminanceLevels); + Span histogram = histogramBuffer.GetSpan(); + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + + for (int x = 0; x < this.sourceWidth; x += this.tileWidth) + { + histogram.Clear(); + Span cdfLutSpan = this.cdfLutBuffer2D.GetRowSpan(index).Slice(cdfX * this.luminanceLevels, this.luminanceLevels); + ref int cdfBase = ref MemoryMarshal.GetReference(cdfLutSpan); + + int xlimit = Math.Min(x + this.tileWidth, this.sourceWidth); + for (int dy = y; dy < endY; dy++) + { + int dyOffset = dy * this.sourceWidth; + for (int dx = x; dx < xlimit; dx++) + { + int luminance = GetLuminance(Unsafe.Add(ref sourceBase, dyOffset + dx), this.luminanceLevels); + histogram[luminance]++; + } + } + + if (this.processor.ClipHistogramEnabled) + { + this.processor.ClipHistogram(histogram, this.processor.ClipLimit); + } + + Unsafe.Add(ref cdfMinBase, cdfX) = this.processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); + + cdfX++; + } + } + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index 5d25bae82..38da9c8d4 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization ParallelRowIterator.IterateRows( workingRect, this.Configuration, - new GrayscaleLevelsRowIntervalAction(workingRect, histogramBuffer, source, this.LuminanceLevels)); + new GrayscaleLevelsRowIntervalOperation(workingRect, histogramBuffer, source, this.LuminanceLevels)); Span histogram = histogramBuffer.GetSpan(); if (this.ClipHistogramEnabled) @@ -77,13 +77,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization ParallelRowIterator.IterateRows( workingRect, this.Configuration, - new CdfApplicationRowIntervalAction(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); + new CdfApplicationRowIntervalOperation(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); } /// /// A implementing the grayscale levels logic for . /// - private readonly struct GrayscaleLevelsRowIntervalAction : IRowIntervalAction + private readonly struct GrayscaleLevelsRowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly IMemoryOwner histogramBuffer; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly int luminanceLevels; [MethodImpl(InliningOptions.ShortMethod)] - public GrayscaleLevelsRowIntervalAction( + public GrayscaleLevelsRowIntervalOperation( in Rectangle bounds, IMemoryOwner histogramBuffer, ImageFrame source, @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// A implementing the cdf application levels logic for . /// - private readonly struct CdfApplicationRowIntervalAction : IRowIntervalAction + private readonly struct CdfApplicationRowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly IMemoryOwner cdfBuffer; @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly float numberOfPixelsMinusCdfMin; [MethodImpl(InliningOptions.ShortMethod)] - public CdfApplicationRowIntervalAction( + public CdfApplicationRowIntervalOperation( in Rectangle bounds, IMemoryOwner cdfBuffer, ImageFrame source, diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index a9b91e837..f26e30e52 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -52,10 +52,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays ParallelRowIterator.IterateRows( interest, configuration, - new RowIntervalAction(configuration, interest, blender, amount, colors, source)); + new RowIntervalOperation(configuration, interest, blender, amount, colors, source)); } - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( Configuration configuration, Rectangle bounds, PixelBlender blender, diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 65a87fbf0..ccb2eed60 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -55,13 +55,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(glowColor); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, configuration, - new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); + new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); } - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( Configuration configuration, Rectangle bounds, IMemoryOwner colors, diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index 11887433c..fd842766d 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -63,13 +63,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(vignetteColor); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( interest, configuration, - new RowIntervalAction(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); + new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); } - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( Configuration configuration, Rectangle bounds, IMemoryOwner colors, diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 402a05249..85ac8dba5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -61,23 +61,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( targetBounds, configuration, - new NearestNeighborRowIntervalAction(this.SourceRectangle, ref matrix, width, source, destination)); + new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination)); return; } using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( targetBounds, configuration, - new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination)); + new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination)); } /// /// A implementing the nearest neighbor resampler logic for . /// - private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction + private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly Matrix3x2 matrix; @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public NearestNeighborRowIntervalAction( + public NearestNeighborRowIntervalOperation( Rectangle bounds, ref Matrix3x2 matrix, int maxX, @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// A implementing the transformation logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Configuration configuration; private readonly TransformKernelMap kernelMap; @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( Configuration configuration, TransformKernelMap kernelMap, ref Matrix3x2 matrix, diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 103c5d3ff..d8c77e8e7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - var rowAction = new RowIntervalAction(ref bounds, source, destination); + var rowAction = new RowIntervalOperation(ref bounds, source, destination); ParallelRowIterator.IterateRows( bounds, @@ -62,20 +62,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// A implementing the processor logic for . /// - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly ImageFrame source; private readonly ImageFrame destination; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The target processing bounds for the current instance. /// The source for the current instance. /// The destination for the current instance. [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction(ref Rectangle bounds, ImageFrame source, ImageFrame destination) + public RowIntervalOperation(ref Rectangle bounds, ImageFrame source, ImageFrame destination) { this.bounds = bounds; this.source = source; diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs index 041f602a5..58be1ef0c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs @@ -79,15 +79,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( source.Bounds(), configuration, - new RowIntervalAction(source)); + new RowIntervalOperation(source)); } - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction(ImageFrame source) => this.source = source; + public RowIntervalOperation(ImageFrame source) => this.source = source; [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 5034b072f..4e5b87b84 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -63,20 +63,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( targetBounds, configuration, - new NearestNeighborRowIntervalAction(ref sourceBounds, ref matrix, width, source, destination)); + new NearestNeighborRowIntervalOperation(ref sourceBounds, ref matrix, width, source, destination)); return; } using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( targetBounds, configuration, - new RowIntervalAction(configuration, kernelMap, ref matrix, width, source, destination)); + new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination)); } - private readonly struct NearestNeighborRowIntervalAction : IRowIntervalAction + private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly Matrix4x4 matrix; @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public NearestNeighborRowIntervalAction( + public NearestNeighborRowIntervalOperation( ref Rectangle bounds, ref Matrix4x4 matrix, int maxX, @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Configuration configuration; private readonly TransformKernelMap kernelMap; @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( Configuration configuration, TransformKernelMap kernelMap, ref Matrix4x4 matrix, diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 02622622d..27a2cca51 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( interest, configuration, - new RowIntervalAction(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination)); + new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination)); return; } @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms base.Dispose(disposing); } - private readonly struct RowIntervalAction : IRowIntervalAction + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly Rectangle sourceBounds; private readonly Rectangle destinationBounds; @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalAction( + public RowIntervalOperation( Rectangle sourceBounds, Rectangle destinationBounds, float widthFactor, diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs index bf03ce319..59cdf4f10 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( source.Bounds(), configuration, - new Rotate180RowIntervalAction(source.Width, source.Height, source, destination)); + new Rotate180RowIntervalOperation(source.Width, source.Height, source, destination)); } /// @@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( source.Bounds(), configuration, - new Rotate270RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination)); + new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination)); } /// @@ -162,10 +162,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelRowIterator.IterateRows( source.Bounds(), configuration, - new Rotate90RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination)); + new Rotate90RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination)); } - private readonly struct Rotate180RowIntervalAction : IRowIntervalAction + private readonly struct Rotate180RowIntervalOperation : IRowIntervalOperation { private readonly int width; private readonly int height; @@ -173,7 +173,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public Rotate180RowIntervalAction( + public Rotate180RowIntervalOperation( int width, int height, ImageFrame source, @@ -201,7 +201,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } - private readonly struct Rotate270RowIntervalAction : IRowIntervalAction + private readonly struct Rotate270RowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly int width; @@ -210,7 +210,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public Rotate270RowIntervalAction( + public Rotate270RowIntervalOperation( Rectangle bounds, int width, int height, @@ -245,7 +245,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } - private readonly struct Rotate90RowIntervalAction : IRowIntervalAction + private readonly struct Rotate90RowIntervalOperation : IRowIntervalOperation { private readonly Rectangle bounds; private readonly int width; @@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public Rotate90RowIntervalAction( + public Rotate90RowIntervalOperation( Rectangle bounds, int width, int height, From 7a8edffd34685fe954eabecdf5dbc95e6db07480 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 9 Feb 2020 00:48:58 +1100 Subject: [PATCH 48/58] Update AdaptiveHistogramEqualizationProcessor{TPixel}.cs --- .../AdaptiveHistogramEqualizationProcessor{TPixel}.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index 64aaa884b..aa97e3a7e 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -473,7 +473,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly Buffer2D cdfLutBuffer2D; private readonly int pixelsInTile; private readonly int sourceWidth; - private readonly int sourceHeight; private readonly int tileWidth; private readonly int tileHeight; private readonly int luminanceLevels; @@ -495,7 +494,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.cdfMinBuffer2D = this.memoryAllocator.Allocate2D(tileCountX, tileCountY); this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D(tileCountX * luminanceLevels, tileCountY); this.sourceWidth = sourceWidth; - this.sourceHeight = sourceHeight; this.tileWidth = tileWidth; this.tileHeight = tileHeight; this.pixelsInTile = tileWidth * tileHeight; From 7609a539717d0cc526813d95f18c49caa282921a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 9 Feb 2020 20:41:31 +1100 Subject: [PATCH 49/58] Update AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs --- ...alizationSlidingWindowProcessor{TPixel}.cs | 425 ++++++++++-------- 1 file changed, 235 insertions(+), 190 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs index 987e4e392..4d0786947 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs @@ -66,191 +66,101 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int halfTileWidth = halfTileHeight; var slidingWindowInfos = new SlidingWindowInfos(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixelInTile); - using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) - { - // Process the inner tiles, which do not require to check the borders. - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: halfTileHeight, - yEnd: source.Height - halfTileHeight, - useFastPath: true, - this.Configuration)); - - // Process the left border of the image. - Parallel.For( - 0, - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: source.Height, - useFastPath: false, - this.Configuration)); - - // Process the right border of the image. - Parallel.For( - source.Width - halfTileWidth, - source.Width, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: source.Height, - useFastPath: false, - this.Configuration)); - - // Process the top border of the image. - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: halfTileHeight, - useFastPath: false, - this.Configuration)); - - // Process the bottom border of the image. - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: source.Height - halfTileHeight, - yEnd: source.Height, - useFastPath: false, - this.Configuration)); - - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); - } - } - - /// - /// Applies the sliding window equalization to one column of the image. The window is moved from top to bottom. - /// Moving the window one pixel down requires to remove one row from the top of the window from the histogram and - /// adding a new row at the bottom. - /// - /// The source image. - /// The memory allocator. - /// The target pixels. - /// about the sliding window dimensions. - /// The y start position. - /// The y end position. - /// if set to true the borders of the image will not be checked. - /// The configuration. - /// Action Delegate. - private Action ProcessSlidingWindow( - ImageFrame source, - MemoryAllocator memoryAllocator, - Buffer2D targetPixels, - SlidingWindowInfos swInfos, - int yStart, - int yEnd, - bool useFastPath, - Configuration configuration) - { - return x => - { - using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner histogramBufferCopy = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner pixelRowBuffer = memoryAllocator.Allocate(swInfos.TileWidth, AllocationOptions.Clean)) - { - Span histogram = histogramBuffer.GetSpan(); - ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - - Span histogramCopy = histogramBufferCopy.GetSpan(); - ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy); - - ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); - - Span pixelRow = pixelRowBuffer.GetSpan(); - ref Vector4 pixelRowBase = ref MemoryMarshal.GetReference(pixelRow); - - // Build the initial histogram of grayscale values. - for (int dy = yStart - swInfos.HalfTileHeight; dy < yStart + swInfos.HalfTileHeight; dy++) - { - if (useFastPath) - { - this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, dy, swInfos.TileWidth, configuration); - } - else - { - this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, dy, swInfos.TileWidth, configuration); - } - - this.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); - } - - for (int y = yStart; y < yEnd; y++) - { - if (this.ClipHistogramEnabled) - { - // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration. - histogram.CopyTo(histogramCopy); - this.ClipHistogram(histogramCopy, this.ClipLimit); - } - - // Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value. - int cdfMin = this.ClipHistogramEnabled - ? this.CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) - : this.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); - - float numberOfPixelsMinusCdfMin = swInfos.PixelInTile - cdfMin; - - // Map the current pixel to the new equalized value. - int luminance = GetLuminance(source[x, y], this.LuminanceLevels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; - targetPixels[x, y].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, source[x, y].ToVector4().W)); - - // Remove top most row from the histogram, mirroring rows which exceeds the borders. - if (useFastPath) - { - this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, y - swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - else - { - this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, y - swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - - this.RemovePixelsFromHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); - - // Add new bottom row to the histogram, mirroring rows which exceeds the borders. - if (useFastPath) - { - this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, y + swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - else - { - this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, y + swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - - this.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); - } - } - }; + // TODO: If the process was able to be switched to operate in parallel rows instead of columns + // then we could take advantage of batching and allocate per-row buffers only once per batch. + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); + + // Process the inner tiles, which do not require to check the borders. + var innerOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: halfTileHeight, + yEnd: source.Height - halfTileHeight, + useFastPath: true); + + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + innerOperation.Invoke); + + // Process the left border of the image. + var leftBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: source.Height, + useFastPath: false); + + Parallel.For( + 0, + halfTileWidth, + parallelOptions, + leftBorderOperation.Invoke); + + // Process the right border of the image. + var rightBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: source.Height, + useFastPath: false); + + Parallel.For( + source.Width - halfTileWidth, + source.Width, + parallelOptions, + rightBorderOperation.Invoke); + + // Process the top border of the image. + var topBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: halfTileHeight, + useFastPath: false); + + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + topBorderOperation.Invoke); + + // Process the bottom border of the image. + var bottomBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: source.Height - halfTileHeight, + yEnd: source.Height, + useFastPath: false); + + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + bottomBorderOperation.Invoke); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } /// @@ -371,6 +281,141 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization } } + /// + /// Applies the sliding window equalization to one column of the image. The window is moved from top to bottom. + /// Moving the window one pixel down requires to remove one row from the top of the window from the histogram and + /// adding a new row at the bottom. + /// + private readonly struct SlidingWindowOperation + { + private readonly Configuration configuration; + private readonly AdaptiveHistogramEqualizationSlidingWindowProcessor processor; + private readonly ImageFrame source; + private readonly MemoryAllocator memoryAllocator; + private readonly Buffer2D targetPixels; + private readonly SlidingWindowInfos swInfos; + private readonly int yStart; + private readonly int yEnd; + private readonly bool useFastPath; + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration. + /// The histogram processor. + /// The source image. + /// The memory allocator. + /// The target pixels. + /// about the sliding window dimensions. + /// The y start position. + /// The y end position. + /// if set to true the borders of the image will not be checked. + [MethodImpl(InliningOptions.ShortMethod)] + public SlidingWindowOperation( + Configuration configuration, + AdaptiveHistogramEqualizationSlidingWindowProcessor processor, + ImageFrame source, + MemoryAllocator memoryAllocator, + Buffer2D targetPixels, + SlidingWindowInfos swInfos, + int yStart, + int yEnd, + bool useFastPath) + { + this.configuration = configuration; + this.processor = processor; + this.source = source; + this.memoryAllocator = memoryAllocator; + this.targetPixels = targetPixels; + this.swInfos = swInfos; + this.yStart = yStart; + this.yEnd = yEnd; + this.useFastPath = useFastPath; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int x) + { + using (IMemoryOwner histogramBuffer = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner histogramBufferCopy = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner cdfBuffer = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner pixelRowBuffer = this.memoryAllocator.Allocate(this.swInfos.TileWidth, AllocationOptions.Clean)) + { + Span histogram = histogramBuffer.GetSpan(); + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + + Span histogramCopy = histogramBufferCopy.GetSpan(); + ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy); + + ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); + + Span pixelRow = pixelRowBuffer.GetSpan(); + ref Vector4 pixelRowBase = ref MemoryMarshal.GetReference(pixelRow); + + // Build the initial histogram of grayscale values. + for (int dy = this.yStart - this.swInfos.HalfTileHeight; dy < this.yStart + this.swInfos.HalfTileHeight; dy++) + { + if (this.useFastPath) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + } + + this.processor.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + } + + for (int y = this.yStart; y < this.yEnd; y++) + { + if (this.processor.ClipHistogramEnabled) + { + // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration. + histogram.CopyTo(histogramCopy); + this.processor.ClipHistogram(histogramCopy, this.processor.ClipLimit); + } + + // Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value. + int cdfMin = this.processor.ClipHistogramEnabled + ? this.processor.CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) + : this.processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); + + float numberOfPixelsMinusCdfMin = this.swInfos.PixelInTile - cdfMin; + + // Map the current pixel to the new equalized value. + int luminance = GetLuminance(this.source[x, y], this.processor.LuminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; + this.targetPixels[x, y].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, this.source[x, y].ToVector4().W)); + + // Remove top most row from the histogram, mirroring rows which exceeds the borders. + if (this.useFastPath) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + + this.processor.RemovePixelsFromHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + + // Add new bottom row to the histogram, mirroring rows which exceeds the borders. + if (this.useFastPath) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + + this.processor.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + } + } + } + } + private class SlidingWindowInfos { public SlidingWindowInfos(int tileWidth, int tileHeight, int halfTileWidth, int halfTileHeight, int pixelInTile) @@ -382,15 +427,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.PixelInTile = pixelInTile; } - public int TileWidth { get; private set; } + public int TileWidth { get; } - public int TileHeight { get; private set; } + public int TileHeight { get; } - public int PixelInTile { get; private set; } + public int PixelInTile { get; } - public int HalfTileWidth { get; private set; } + public int HalfTileWidth { get; } - public int HalfTileHeight { get; private set; } + public int HalfTileHeight { get; } } } } From c946849c320b4f564943f06bda1b730dee8c10ba Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 9 Feb 2020 23:00:14 +1100 Subject: [PATCH 50/58] Fix arguments order and tests --- .../Advanced/ParallelRowIterator.cs | 130 +------- src/ImageSharp/ImageFrame{TPixel}.cs | 5 +- .../BinaryThresholdProcessor{TPixel}.cs | 6 +- .../Convolution/BokehBlurProcessor{TPixel}.cs | 20 +- .../Convolution2DProcessor{TPixel}.cs | 5 +- .../Convolution2PassProcessor{TPixel}.cs | 10 +- .../ConvolutionProcessor{TPixel}.cs | 6 +- .../EdgeDetectorCompassProcessor{TPixel}.cs | 5 +- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 5 +- .../Effects/OilPaintingProcessor{TPixel}.cs | 5 +- ...lRowDelegateProcessor{TPixel,TDelegate}.cs | 5 +- .../Filters/FilterProcessor{TPixel}.cs | 5 +- ...eHistogramEqualizationProcessor{TPixel}.cs | 11 +- ...lHistogramEqualizationProcessor{TPixel}.cs | 10 +- .../BackgroundColorProcessor{TPixel}.cs | 5 +- .../Overlays/GlowProcessor{TPixel}.cs | 5 +- .../Overlays/VignetteProcessor{TPixel}.cs | 5 +- .../AffineTransformProcessor{TPixel}.cs | 10 +- .../Transforms/FlipProcessor{TPixel}.cs | 5 +- .../ProjectiveTransformProcessor{TPixel}.cs | 10 +- .../Resize/ResizeProcessor{TPixel}.cs | 5 +- .../Transforms/RotateProcessor{TPixel}.cs | 17 +- .../Helpers/ParallelRowIteratorTests.cs | 277 +++++++++++------- .../TestUtilities/TestImageExtensions.cs | 53 ++-- 24 files changed, 309 insertions(+), 311 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index beef99c1c..d7939478b 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -21,15 +21,15 @@ namespace SixLabors.ImageSharp.Advanced /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// /// The type of row operation to perform. - /// The . /// The to get the parallel settings from. - /// The method body defining the iteration logic on a single . + /// The . + /// The operation defining the iteration logic on a single . [MethodImpl(InliningOptions.ShortMethod)] - public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) + public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) where T : struct, IRowIntervalOperation { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, in body); + IterateRows(rectangle, in parallelSettings, in operation); } /// @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Advanced /// The type of row operation to perform. /// The . /// The . - /// The method body defining the iteration logic on a single . + /// The operation defining the iteration logic on a single . public static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, @@ -81,10 +81,10 @@ namespace SixLabors.ImageSharp.Advanced /// /// The type of row operation to perform. /// The type of buffer elements. - /// The . /// The to get the parallel settings from. - /// The method body defining the iteration logic on a single . - public static void IterateRows(Rectangle rectangle, Configuration configuration, in T operation) + /// The . + /// The operation defining the iteration logic on a single . + public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) where T : struct, IRowIntervalOperation where TBuffer : unmanaged { @@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp.Advanced /// The type of buffer elements. /// The . /// The . - /// The method body defining the iteration logic on a single . + /// The operation defining the iteration logic on a single . public static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, @@ -143,118 +143,6 @@ namespace SixLabors.ImageSharp.Advanced rowOperation.Invoke); } - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - /// The . - /// The to get the parallel settings from. - /// The method body defining the iteration logic on a single . - internal static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) - { - var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, body); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - /// The . - /// The . - /// The method body defining the iteration logic on a single . - internal static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action body) - { - ValidateRectangle(rectangle); - - int top = rectangle.Top; - int bottom = rectangle.Bottom; - int width = rectangle.Width; - int height = rectangle.Height; - - int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - var rows = new RowInterval(top, bottom); - body(rows); - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); - var rowAction = new WrappingRowIntervalOperation(in rowInfo, body); - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - rowAction.Invoke); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - internal static void IterateRows( - Rectangle rectangle, - Configuration configuration, - Action> body) - where TBuffer : unmanaged - { - var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, body); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - internal static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action> body) - where TBuffer : unmanaged - { - ValidateRectangle(rectangle); - - int top = rectangle.Top; - int bottom = rectangle.Bottom; - int width = rectangle.Width; - int height = rectangle.Height; - - int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - MemoryAllocator allocator = parallelSettings.MemoryAllocator; - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - var rows = new RowInterval(top, bottom); - using (IMemoryOwner buffer = allocator.Allocate(width)) - { - body(rows, buffer.Memory); - } - - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); - var rowAction = new WrappingRowIntervalBufferOperation(in rowInfo, allocator, body); - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - rowAction.Invoke); - } - [MethodImpl(InliningOptions.ShortMethod)] private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index e3dbad505..a2de8d671 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -259,11 +259,12 @@ namespace SixLabors.ImageSharp } var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); + var operation = new RowIntervalOperation(this, target, configuration); ParallelRowIterator.IterateRows( - this.Bounds(), configuration, - new RowIntervalOperation(this, target, configuration)); + this.Bounds(), + in operation); return target; } diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 129edbb03..52be6abe2 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -50,11 +50,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization bool isAlphaOnly = typeof(TPixel) == typeof(A8); var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - + var operation = new RowIntervalOperation(source, upper, lower, threshold, startX, endX, isAlphaOnly); ParallelRowIterator.IterateRows( - workingRect, configuration, - new RowIntervalOperation(source, upper, lower, threshold, startX, endX, isAlphaOnly)); + workingRect, + in operation); } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 3fb62ed19..16acc2407 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -268,10 +268,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution protected override void OnFrameApply(ImageFrame source) { // Preliminary gamma highlight pass + var gammaOperation = new ApplyGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); ParallelRowIterator.IterateRows( - this.SourceRectangle, this.Configuration, - new ApplyGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); + this.SourceRectangle, + in gammaOperation); // Create a 0-filled buffer to use to store the result of the component convolutions using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean); @@ -282,10 +283,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution float inverseGamma = 1 / this.gamma; // Apply the inverse gamma exposure pass, and write the final pixel data + var operation = new ApplyInverseGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma); ParallelRowIterator.IterateRows( - this.SourceRectangle, this.Configuration, - new ApplyInverseGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); + this.SourceRectangle, + in operation); } /// @@ -314,16 +316,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Vector4 parameters = Unsafe.Add(ref paramsRef, i); // Compute the vertical 1D convolution + var verticalOperation = new ApplyVerticalConvolutionRowIntervalOperation(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel); ParallelRowIterator.IterateRows( - sourceRectangle, configuration, - new ApplyVerticalConvolutionRowIntervalOperation(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); + sourceRectangle, + in verticalOperation); // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer + var horizontalOperation = new ApplyHorizontalConvolutionRowIntervalOperation(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W); ParallelRowIterator.IterateRows( - sourceRectangle, configuration, - new ApplyHorizontalConvolutionRowIntervalOperation(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); + sourceRectangle, + in horizontalOperation); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 0340482fe..d8179c6d5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -65,11 +65,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha)); + interest, + in operation); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 3cbbf8c46..fb477e2d6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -64,16 +64,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // Horizontal convolution + var horizontalOperation = new RowIntervalOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); + interest, + in horizontalOperation); // Vertical convolution + var verticalOperation = new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); + interest, + in verticalOperation); } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 2774a2f88..feb27ac62 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -56,11 +56,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - + var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha)); + interest, + in operation); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index f390ee1ff..fde669ea8 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -102,10 +102,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution processor.Apply(pass); } + var operation = new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX); ParallelRowIterator.IterateRows( - Rectangle.FromLTRB(minX, minY, maxX, maxY), this.Configuration, - new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX)); + Rectangle.FromLTRB(minX, minY, maxX, maxY), + in operation); } } diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 88fa6bec3..c1ce30cae 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -99,10 +99,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing "Cannot draw image because the source image does not overlap the target image."); } + var operation = new RowIntervalOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity); ParallelRowIterator.IterateRows( - workingRect, configuration, - new RowIntervalOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity)); + workingRect, + in operation); } /// diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 1c3be4e63..50c0a22d3 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -47,10 +47,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects source.CopyTo(targetPixels); + var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels); ParallelRowIterator.IterateRows( - this.SourceRectangle, this.Configuration, - new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels)); + this.SourceRectangle, + in operation); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index 3f8dcd8d9..a22af8ce2 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -50,11 +50,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var operation = new RowIntervalOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate); ParallelRowIterator.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate)); + interest, + in operation); } /// diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index 159a1f981..cae8b14b8 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -36,11 +36,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var operation = new RowIntervalOperation(interest.X, source, this.definition.Matrix, this.Configuration); ParallelRowIterator.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest.X, source, this.definition.Matrix, this.Configuration)); + interest, + in operation); } /// diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index aa97e3a7e..eb666a4f1 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -80,10 +80,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization yStart += tileHeight; } + var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source); ParallelRowIterator.IterateRows( - new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), this.Configuration, - new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source)); + new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), + in operation); ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); @@ -510,7 +511,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public void CalculateLookupTables(ImageFrame source, HistogramEqualizationProcessor processor) { - var rowOperation = new RowIntervalOperation( + var operation = new RowIntervalOperation( processor, this.memoryAllocator, this.cdfMinBuffer2D, @@ -522,9 +523,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization source); ParallelRowIterator.IterateRows( - new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), this.configuration, - in rowOperation); + new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), + in operation); } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index 38da9c8d4..d7ea80737 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -52,10 +52,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); // Build the histogram of the grayscale levels + var grayscaleOperation = new GrayscaleLevelsRowIntervalOperation(workingRect, histogramBuffer, source, this.LuminanceLevels); ParallelRowIterator.IterateRows( - workingRect, this.Configuration, - new GrayscaleLevelsRowIntervalOperation(workingRect, histogramBuffer, source, this.LuminanceLevels)); + workingRect, + in grayscaleOperation); Span histogram = histogramBuffer.GetSpan(); if (this.ClipHistogramEnabled) @@ -74,10 +75,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; // Apply the cdf to each pixel of the image + var cdfOperation = new CdfApplicationRowIntervalOperation(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); ParallelRowIterator.IterateRows( - workingRect, this.Configuration, - new CdfApplicationRowIntervalOperation(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); + workingRect, + in cdfOperation); } /// diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index f26e30e52..b796016d1 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -49,10 +49,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); + var operation = new RowIntervalOperation(configuration, interest, blender, amount, colors, source); ParallelRowIterator.IterateRows( - interest, configuration, - new RowIntervalOperation(configuration, interest, blender, amount, colors, source)); + interest, + in operation); } private readonly struct RowIntervalOperation : IRowIntervalOperation diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index ccb2eed60..83d9bd1ef 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -55,10 +55,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(glowColor); + var operation = new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); ParallelRowIterator.IterateRows( - interest, configuration, - new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); + interest, + in operation); } private readonly struct RowIntervalOperation : IRowIntervalOperation diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index fd842766d..b36e6b534 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -63,10 +63,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(vignetteColor); + var operation = new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); ParallelRowIterator.IterateRows( - interest, configuration, - new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); + interest, + in operation); } private readonly struct RowIntervalOperation : IRowIntervalOperation diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 85ac8dba5..2b579541c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -58,20 +58,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { + var nnOperation = new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( - targetBounds, configuration, - new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination)); + targetBounds, + in nnOperation); return; } using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); + var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( - targetBounds, configuration, - new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination)); + targetBounds, + in operation); } /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs index 58be1ef0c..877fa074e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs @@ -76,10 +76,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void FlipY(ImageFrame source, Configuration configuration) { + var operation = new RowIntervalOperation(source); ParallelRowIterator.IterateRows( - source.Bounds(), configuration, - new RowIntervalOperation(source)); + source.Bounds(), + in operation); } private readonly struct RowIntervalOperation : IRowIntervalOperation diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 4e5b87b84..3969a8c3e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -60,20 +60,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Rectangle sourceBounds = this.SourceRectangle; + var nnOperation = new NearestNeighborRowIntervalOperation(ref sourceBounds, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( - targetBounds, configuration, - new NearestNeighborRowIntervalOperation(ref sourceBounds, ref matrix, width, source, destination)); + targetBounds, + in nnOperation); return; } using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); + var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( - targetBounds, configuration, - new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination)); + targetBounds, + in operation); } private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 27a2cca51..53810a5cc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -95,10 +95,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; + var operation = new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination); ParallelRowIterator.IterateRows( - interest, configuration, - new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination)); + interest, + in operation); return; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs index 59cdf4f10..086314a26 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class RotateProcessor : AffineTransformProcessor where TPixel : struct, IPixel { - private float degrees; + private readonly float degrees; /// /// Initializes a new instance of the class. @@ -131,10 +131,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) { + var operation = new Rotate180RowIntervalOperation(source.Width, source.Height, source, destination); ParallelRowIterator.IterateRows( - source.Bounds(), configuration, - new Rotate180RowIntervalOperation(source.Width, source.Height, source, destination)); + source.Bounds(), + in operation); } /// @@ -145,10 +146,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) { + var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); ParallelRowIterator.IterateRows( - source.Bounds(), configuration, - new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination)); + source.Bounds(), + in operation); } /// @@ -159,10 +161,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) { + var operation = new Rotate90RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); ParallelRowIterator.IterateRows( - source.Bounds(), configuration, - new Rotate90RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination)); + source.Bounds(), + in operation); } private readonly struct Rotate180RowIntervalOperation : IRowIntervalOperation diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 243ffe220..80ac384fd 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -10,7 +10,6 @@ using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; - using Xunit; using Xunit.Abstractions; @@ -30,20 +29,20 @@ namespace SixLabors.ImageSharp.Tests.Helpers /// public static TheoryData IterateRows_OverMinimumPixelsLimit_Data = new TheoryData - { - { 1, 0, 100, -1, 100, 1 }, - { 2, 0, 9, 5, 4, 2 }, - { 4, 0, 19, 5, 4, 4 }, - { 2, 10, 19, 5, 4, 2 }, - { 4, 0, 200, 50, 50, 4 }, - { 4, 123, 323, 50, 50, 4 }, - { 4, 0, 1201, 301, 298, 4 }, - { 8, 10, 236, 29, 23, 8 }, - { 16, 0, 209, 14, 13, 15 }, - { 24, 0, 209, 9, 2, 24 }, - { 32, 0, 209, 7, 6, 30 }, - { 64, 0, 209, 4, 1, 53 }, - }; + { + { 1, 0, 100, -1, 100, 1 }, + { 2, 0, 9, 5, 4, 2 }, + { 4, 0, 19, 5, 4, 4 }, + { 2, 10, 19, 5, 4, 2 }, + { 4, 0, 200, 50, 50, 4 }, + { 4, 123, 323, 50, 50, 4 }, + { 4, 0, 1201, 301, 298, 4 }, + { 8, 10, 236, 29, 23, 8 }, + { 16, 0, 209, 14, 13, 15 }, + { 24, 0, 209, 9, 2, 24 }, + { 32, 0, 209, 7, 6, 30 }, + { 64, 0, 209, 4, 1, 53 }, + }; [Theory] [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] @@ -64,20 +63,24 @@ namespace SixLabors.ImageSharp.Tests.Helpers int actualNumberOfSteps = 0; - ParallelRowIterator.IterateRows( - rectangle, - parallelSettings, - rows => - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); + void RowAction(RowInterval rows) + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); + + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; - int step = rows.Max - rows.Min; - int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRows( + rectangle, + in parallelSettings, + in operation); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } @@ -102,16 +105,20 @@ namespace SixLabors.ImageSharp.Tests.Helpers int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); var actualData = new int[maxY]; + void RowAction(RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + } + + var operation = new TestRowIntervalOperation(RowAction); + ParallelRowIterator.IterateRows( rectangle, - parallelSettings, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } - }); + in parallelSettings, + in operation); Assert.Equal(expectedData, actualData); } @@ -136,22 +143,27 @@ namespace SixLabors.ImageSharp.Tests.Helpers var bufferHashes = new ConcurrentBag(); int actualNumberOfSteps = 0; - ParallelRowIterator.IterateRows( - rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); - bufferHashes.Add(buffer.GetHashCode()); + void RowAction(RowInterval rows, Memory buffer) + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); + + bufferHashes.Add(buffer.GetHashCode()); + + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } - int step = rows.Max - rows.Min; - int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + var operation = new TestRowIntervalOperation(RowAction); - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + ParallelRowIterator.IterateRows, Vector4>( + rectangle, + in parallelSettings, + in operation); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); @@ -179,31 +191,35 @@ namespace SixLabors.ImageSharp.Tests.Helpers int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); var actualData = new int[maxY]; - ParallelRowIterator.IterateRows( + void RowAction(RowInterval rows, Memory buffer) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRows, Vector4>( rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } - }); + in parallelSettings, + in operation); Assert.Equal(expectedData, actualData); } public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = new TheoryData - { - { 2, 200, 50, 2, 1, -1, 2 }, - { 2, 200, 200, 1, 1, -1, 1 }, - { 4, 200, 100, 4, 2, 2, 2 }, - { 4, 300, 100, 8, 3, 3, 2 }, - { 2, 5000, 1, 4500, 1, -1, 4500 }, - { 2, 5000, 1, 5000, 1, -1, 5000 }, - { 2, 5000, 1, 5001, 2, 2501, 2500 }, - }; + { + { 2, 200, 50, 2, 1, -1, 2 }, + { 2, 200, 200, 1, 1, -1, 1 }, + { 4, 200, 100, 4, 2, 2, 2 }, + { 4, 300, 100, 8, 3, 3, 2 }, + { 2, 5000, 1, 4500, 1, -1, 4500 }, + { 2, 5000, 1, 5000, 1, -1, 5000 }, + { 2, 5000, 1, 5001, 2, 2501, 2500 }, + }; [Theory] [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] @@ -225,20 +241,24 @@ namespace SixLabors.ImageSharp.Tests.Helpers int actualNumberOfSteps = 0; - ParallelRowIterator.IterateRows( - rectangle, - parallelSettings, - rows => - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); + void RowAction(RowInterval rows) + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); + + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + var operation = new TestRowIntervalOperation(RowAction); - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + ParallelRowIterator.IterateRows( + rectangle, + in parallelSettings, + in operation); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } @@ -262,33 +282,38 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rectangle = new Rectangle(0, 0, width, height); int actualNumberOfSteps = 0; - ParallelRowIterator.IterateRows( - rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + void RowAction(RowInterval rows, Memory buffer) + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRows, Vector4>( + rectangle, + in parallelSettings, + in operation); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } public static readonly TheoryData IterateRectangularBuffer_Data = new TheoryData - { - { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox - { 2, 582, 453, 10, 10, 291, 226 }, - { 16, 582, 453, 10, 10, 291, 226 }, - { 16, 582, 453, 10, 10, 1, 226 }, - { 16, 1, 453, 0, 10, 1, 226 }, - }; + { + { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox + { 2, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 1, 226 }, + { 16, 1, 453, 0, 10, 1, 226 }, + }; [Theory] [MemberData(nameof(IterateRectangularBuffer_Data))] @@ -325,17 +350,21 @@ namespace SixLabors.ImageSharp.Tests.Helpers // Fill actual data using IterateRows: var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); + void RowAction(RowInterval rows) + { + this.output.WriteLine(rows.ToString()); + for (int y = rows.Min; y < rows.Max; y++) + { + FillRow(y, actual); + } + } + + var operation = new TestRowIntervalOperation(RowAction); + ParallelRowIterator.IterateRows( rect, settings, - rows => - { - this.output.WriteLine(rows.ToString()); - for (int y = rows.Min; y < rows.Max; y++) - { - FillRow(y, actual); - } - }); + in operation); // Assert: TestImageExtensions.CompareBuffers(expected.GetSpan(), actual.GetSpan()); @@ -353,8 +382,14 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rect = new Rectangle(0, 0, width, height); + void RowAction(RowInterval rows) + { + } + + var operation = new TestRowIntervalOperation(RowAction); + ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRows(rect, parallelSettings, rows => { })); + () => ParallelRowIterator.IterateRows(rect, in parallelSettings, in operation)); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } @@ -370,10 +405,38 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rect = new Rectangle(0, 0, width, height); + void RowAction(RowInterval rows, Memory memory) + { + } + + var operation = new TestRowIntervalOperation(RowAction); + ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRows(rect, parallelSettings, (rows, memory) => { })); + () => ParallelRowIterator.IterateRows, Rgba32>(rect, in parallelSettings, in operation)); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } + + private readonly struct TestRowIntervalOperation : IRowIntervalOperation + { + private readonly Action action; + + public TestRowIntervalOperation(Action action) + => this.action = action; + + public void Invoke(in RowInterval rows) => this.action(rows); + } + + private readonly struct TestRowIntervalOperation : IRowIntervalOperation + where TBuffer : unmanaged + { + private readonly Action> action; + + public TestRowIntervalOperation(Action> action) + => this.action = action; + + public void Invoke(in RowInterval rows, Memory memory) + => this.action(rows, memory); + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 9aaca3e3f..d570b4d05 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -702,25 +702,44 @@ namespace SixLabors.ImageSharp.Tests { Rectangle sourceRectangle = this.SourceRectangle; Configuration configuration = this.Configuration; - ParallelRowIterator.IterateRows( - sourceRectangle, + + var operation = new RowOperation(configuration, sourceRectangle, source); + + ParallelRowIterator.IterateRows( configuration, - (rows, temp) => + sourceRectangle, + in operation); + } + + private readonly struct RowOperation : IRowIntervalOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly ImageFrame source; + + public RowOperation(Configuration configuration, Rectangle bounds, ImageFrame source) + { + this.configuration = configuration; + this.bounds = bounds; + this.source = source; + } + + public void Invoke(in RowInterval rows, Memory memory) + { + Span tempSpan = memory.Span; + for (int y = rows.Min; y < rows.Max; y++) + { + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, tempSpan, PixelConversionModifiers.Scale); + for (int i = 0; i < tempSpan.Length; i++) { - Span tempSpan = temp.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span rowSpan = source.GetPixelRowSpan(y).Slice(sourceRectangle.Left, sourceRectangle.Width); - PixelOperations.Instance.ToVector4(configuration, rowSpan, tempSpan, PixelConversionModifiers.Scale); - for (int i = 0; i < tempSpan.Length; i++) - { - ref Vector4 v = ref tempSpan[i]; - v.W = 1F; - } - - PixelOperations.Instance.FromVector4Destructive(configuration, tempSpan, rowSpan, PixelConversionModifiers.Scale); - } - }); + ref Vector4 v = ref tempSpan[i]; + v.W = 1F; + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, tempSpan, rowSpan, PixelConversionModifiers.Scale); + } + } } } } From b247bac77f4dad011d9719339afeb750a8c242cb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Feb 2020 00:02:05 +1100 Subject: [PATCH 51/58] Use Span as buffer param --- .../Advanced/IRowIntervalOperation.cs | 81 ------------- .../IRowIntervalOperation{TBuffer}.cs | 78 +------------ .../Advanced/ParallelRowIterator.Wrappers.cs | 110 ++++++++++++++++++ .../Advanced/ParallelRowIterator.cs | 4 +- .../Convolution/BokehBlurProcessor{TPixel}.cs | 11 +- .../Convolution2DProcessor{TPixel}.cs | 14 +-- .../Convolution2PassProcessor{TPixel}.cs | 14 +-- .../ConvolutionProcessor{TPixel}.cs | 14 +-- ...lRowDelegateProcessor{TPixel,TDelegate}.cs | 12 +- .../Filters/FilterProcessor{TPixel}.cs | 12 +- .../Overlays/GlowProcessor{TPixel}.cs | 7 +- .../Overlays/VignetteProcessor{TPixel}.cs | 7 +- .../AffineTransformProcessor{TPixel}.cs | 9 +- .../ProjectiveTransformProcessor{TPixel}.cs | 9 +- .../Helpers/ParallelRowIteratorTests.cs | 26 ++--- .../TestUtilities/TestImageExtensions.cs | 11 +- 16 files changed, 175 insertions(+), 244 deletions(-) create mode 100644 src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation.cs b/src/ImageSharp/Advanced/IRowIntervalOperation.cs index 9aa79e730..3e1b08621 100644 --- a/src/ImageSharp/Advanced/IRowIntervalOperation.cs +++ b/src/ImageSharp/Advanced/IRowIntervalOperation.cs @@ -18,85 +18,4 @@ namespace SixLabors.ImageSharp.Advanced /// The row interval. void Invoke(in RowInterval rows); } - - internal readonly struct WrappingRowIntervalInfo - { - public readonly int MinY; - public readonly int MaxY; - public readonly int StepY; - public readonly int MaxX; - - public WrappingRowIntervalInfo(int minY, int maxY, int stepY) - : this(minY, maxY, stepY, 0) - { - } - - public WrappingRowIntervalInfo(int minY, int maxY, int stepY, int maxX) - { - this.MinY = minY; - this.MaxY = maxY; - this.StepY = stepY; - this.MaxX = maxX; - } - } - - internal readonly struct WrappingRowIntervalOperation - { - private readonly WrappingRowIntervalInfo info; - private readonly Action action; - - [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowIntervalOperation(in WrappingRowIntervalInfo info, Action action) - { - this.info = info; - this.action = action; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) - { - int yMin = this.info.MinY + (i * this.info.StepY); - - if (yMin >= this.info.MaxY) - { - return; - } - - int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); - var rows = new RowInterval(yMin, yMax); - - this.action(rows); - } - } - - internal readonly struct WrappingRowIntervalOperation - where T : struct, IRowIntervalOperation - { - private readonly WrappingRowIntervalInfo info; - private readonly T operation; - - [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowIntervalOperation(in WrappingRowIntervalInfo info, in T operation) - { - this.info = info; - this.operation = operation; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) - { - int yMin = this.info.MinY + (i * this.info.StepY); - - if (yMin >= this.info.MaxY) - { - return; - } - - int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); - var rows = new RowInterval(yMin, yMax); - - // Skip the safety copy when invoking a potentially impure method on a readonly field - Unsafe.AsRef(this.operation).Invoke(in rows); - } - } } diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs index 18ebc9fb9..c18842a92 100644 --- a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs @@ -19,81 +19,7 @@ namespace SixLabors.ImageSharp.Advanced /// Invokes the method passing the row interval and a buffer. /// /// The row interval. - /// The contiguous region of memory. - void Invoke(in RowInterval rows, Memory memory); - } - - internal readonly struct WrappingRowIntervalBufferOperation - where TBuffer : unmanaged - { - private readonly WrappingRowIntervalInfo info; - private readonly MemoryAllocator allocator; - private readonly Action> action; - - [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowIntervalBufferOperation( - in WrappingRowIntervalInfo info, - MemoryAllocator allocator, - Action> action) - { - this.info = info; - this.allocator = allocator; - this.action = action; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) - { - int yMin = this.info.MinY + (i * this.info.StepY); - - if (yMin >= this.info.MaxY) - { - return; - } - - int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); - var rows = new RowInterval(yMin, yMax); - - using IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX); - this.action(rows, buffer.Memory); - } - } - - internal readonly struct WrappingRowIntervalBufferOperation - where T : struct, IRowIntervalOperation - where TBuffer : unmanaged - { - private readonly WrappingRowIntervalInfo info; - private readonly MemoryAllocator allocator; - private readonly T operation; - - [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowIntervalBufferOperation( - in WrappingRowIntervalInfo info, - MemoryAllocator allocator, - in T operation) - { - this.info = info; - this.allocator = allocator; - this.operation = operation; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) - { - int yMin = this.info.MinY + (i * this.info.StepY); - - if (yMin >= this.info.MaxY) - { - return; - } - - int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); - var rows = new RowInterval(yMin, yMax); - - using IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX); - - Unsafe.AsRef(this.operation).Invoke(in rows, buffer.Memory); - } + /// The contiguous region of memory. + void Invoke(in RowInterval rows, Span span); } } diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs new file mode 100644 index 000000000..4abcd1a3e --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs @@ -0,0 +1,110 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Utility methods for batched processing of pixel row intervals. + /// Parallel execution is optimized for image processing based on values defined + /// or . + /// Using this class is preferred over direct usage of utility methods. + /// + public static partial class ParallelRowIterator + { + private readonly struct WrappingRowIntervalInfo + { + public readonly int MinY; + public readonly int MaxY; + public readonly int StepY; + public readonly int MaxX; + + public WrappingRowIntervalInfo(int minY, int maxY, int stepY) + : this(minY, maxY, stepY, 0) + { + } + + public WrappingRowIntervalInfo(int minY, int maxY, int stepY, int maxX) + { + this.MinY = minY; + this.MaxY = maxY; + this.StepY = stepY; + this.MaxX = maxX; + } + } + + private readonly struct WrappingRowIntervalOperation + where T : struct, IRowIntervalOperation + { + private readonly WrappingRowIntervalInfo info; + private readonly T operation; + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowIntervalOperation(in WrappingRowIntervalInfo info, in T operation) + { + this.info = info; + this.operation = operation; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.info.MinY + (i * this.info.StepY); + + if (yMin >= this.info.MaxY) + { + return; + } + + int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + var rows = new RowInterval(yMin, yMax); + + // Skip the safety copy when invoking a potentially impure method on a readonly field + Unsafe.AsRef(this.operation).Invoke(in rows); + } + } + + private readonly struct WrappingRowIntervalBufferOperation + where T : struct, IRowIntervalOperation + where TBuffer : unmanaged + { + private readonly WrappingRowIntervalInfo info; + private readonly MemoryAllocator allocator; + private readonly T operation; + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowIntervalBufferOperation( + in WrappingRowIntervalInfo info, + MemoryAllocator allocator, + in T operation) + { + this.info = info; + this.allocator = allocator; + this.operation = operation; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.info.MinY + (i * this.info.StepY); + + if (yMin >= this.info.MaxY) + { + return; + } + + int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + var rows = new RowInterval(yMin, yMax); + + using IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX); + + Unsafe.AsRef(this.operation).Invoke(in rows, buffer.Memory.Span); + } + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index d7939478b..e9d522966 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Advanced /// or . /// Using this class is preferred over direct usage of utility methods. /// - public static class ParallelRowIterator + public static partial class ParallelRowIterator { /// /// Iterate through the rows of a rectangle in optimized batches defined by -s. @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Advanced var rows = new RowInterval(top, bottom); using (IMemoryOwner buffer = allocator.Allocate(width)) { - Unsafe.AsRef(operation).Invoke(rows, buffer.Memory); + Unsafe.AsRef(operation).Invoke(rows, buffer.Memory.Span); } return; diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 16acc2407..e388fdaad 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -448,16 +448,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(in RowInterval rows, Span span) { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; - for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); for (int x = 0; x < this.bounds.Width; x++) { @@ -467,7 +464,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution v.Z = MathF.Pow(v.Z, this.gamma); } - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan.Slice(0, length), targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index d8179c6d5..1c4987c79 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -113,16 +113,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(in RowInterval rows, Span span) { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); if (this.preserveAlpha) { @@ -132,7 +130,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution in this.kernelY, in this.kernelX, this.sourcePixels, - ref vectorSpanRef, + ref spanRef, y, x, this.bounds.Y, @@ -149,7 +147,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution in this.kernelY, in this.kernelX, this.sourcePixels, - ref vectorSpanRef, + ref spanRef, y, x, this.bounds.Y, @@ -159,7 +157,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } } - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index fb477e2d6..33a8ab7d1 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -109,11 +109,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(in RowInterval rows, Span span) { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); int maxY = this.bounds.Bottom - 1; int maxX = this.bounds.Right - 1; @@ -121,7 +119,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); if (this.preserveAlpha) { @@ -130,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution DenseMatrixUtils.Convolve3( in this.kernel, this.sourcePixels, - ref vectorSpanRef, + ref spanRef, y, x, this.bounds.Y, @@ -146,7 +144,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution DenseMatrixUtils.Convolve4( in this.kernel, this.sourcePixels, - ref vectorSpanRef, + ref spanRef, y, x, this.bounds.Y, @@ -156,7 +154,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } } - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index feb27ac62..542ee389b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -100,16 +100,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(in RowInterval rows, Span span) { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); if (this.preserveAlpha) { @@ -118,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution DenseMatrixUtils.Convolve3( in this.kernel, this.sourcePixels, - ref vectorSpanRef, + ref spanRef, y, x, this.bounds.Y, @@ -134,7 +132,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution DenseMatrixUtils.Convolve4( in this.kernel, this.sourcePixels, - ref vectorSpanRef, + ref spanRef, y, x, this.bounds.Y, @@ -144,7 +142,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } } - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index a22af8ce2..44ade727a 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -86,19 +86,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(in RowInterval rows, Span span) { for (int y = rows.Min; y < rows.Max; y++) { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, vectorSpan, this.modifiers); + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers); // Run the user defined pixel shader to the current row of pixels - Unsafe.AsRef(this.rowProcessor).Invoke(vectorSpan, new Point(this.startX, y)); + Unsafe.AsRef(this.rowProcessor).Invoke(span, new Point(this.startX, y)); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan, this.modifiers); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, this.modifiers); } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index cae8b14b8..19142ceb0 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -69,18 +69,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(in RowInterval rows, Span span) { for (int y = rows.Min; y < rows.Max; y++) { - Span vectorSpan = memory.Span; - int length = vectorSpan.Length; - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, vectorSpan); + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span); - Vector4Utils.Transform(vectorSpan, ref Unsafe.AsRef(this.matrix)); + Vector4Utils.Transform(span, ref Unsafe.AsRef(this.matrix)); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan, rowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 83d9bd1ef..21a4e1345 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -95,9 +95,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(in RowInterval rows, Span span) { - Span amountsSpan = memory.Span; Span colorSpan = this.colors.GetSpan(); for (int y = rows.Min; y < rows.Max; y++) @@ -105,7 +104,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays for (int i = 0; i < this.bounds.Width; i++) { float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); - amountsSpan[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); + span[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); } Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); @@ -115,7 +114,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays destination, destination, colorSpan, - amountsSpan); + span); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index b36e6b534..3515a2891 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -103,9 +103,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(in RowInterval rows, Span span) { - Span amountsSpan = memory.Span; Span colorSpan = this.colors.GetSpan(); for (int y = rows.Min; y < rows.Max; y++) @@ -113,7 +112,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays for (int i = 0; i < this.bounds.Width; i++) { float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); - amountsSpan[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); + span[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); } Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); @@ -123,7 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays destination, destination, colorSpan, - amountsSpan); + span); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 2b579541c..0d9055f34 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -154,13 +154,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(in RowInterval rows, Span span) { - Span vectorSpan = memory.Span; for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = this.destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, vectorSpan); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, span); ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); @@ -175,12 +174,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ref ySpanRef, ref xSpanRef, this.source.PixelBuffer, - vectorSpan); + span); } PixelOperations.Instance.FromVector4Destructive( this.configuration, - vectorSpan, + span, targetRowSpan); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 3969a8c3e..83bc540eb 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -150,13 +150,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(in RowInterval rows, Span span) { - Span vectorSpan = memory.Span; for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = this.destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, vectorSpan); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, span); ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); @@ -171,12 +170,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ref ySpanRef, ref xSpanRef, this.source.PixelBuffer, - vectorSpan); + span); } PixelOperations.Instance.FromVector4Destructive( this.configuration, - vectorSpan, + span, targetRowSpan); } } diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 80ac384fd..3f5e9040d 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Concurrent; using System.Linq; using System.Numerics; using System.Threading; @@ -17,6 +16,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers { public class ParallelRowIteratorTests { + public delegate void RowIntervalAction(RowInterval rows, Span span); + private readonly ITestOutputHelper output; public ParallelRowIteratorTests(ITestOutputHelper output) @@ -140,17 +141,13 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rectangle = new Rectangle(0, minY, 10, maxY - minY); - var bufferHashes = new ConcurrentBag(); - int actualNumberOfSteps = 0; - void RowAction(RowInterval rows, Memory buffer) + void RowAction(RowInterval rows, Span buffer) { Assert.True(rows.Min >= minY); Assert.True(rows.Max <= maxY); - bufferHashes.Add(buffer.GetHashCode()); - int step = rows.Max - rows.Min; int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; @@ -166,9 +163,6 @@ namespace SixLabors.ImageSharp.Tests.Helpers in operation); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); - - int numberOfDifferentBuffers = bufferHashes.Distinct().Count(); - Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers); } [Theory] @@ -191,7 +185,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); var actualData = new int[maxY]; - void RowAction(RowInterval rows, Memory buffer) + void RowAction(RowInterval rows, Span buffer) { for (int y = rows.Min; y < rows.Max; y++) { @@ -283,7 +277,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers int actualNumberOfSteps = 0; - void RowAction(RowInterval rows, Memory buffer) + void RowAction(RowInterval rows, Span buffer) { Assert.True(rows.Min >= 0); Assert.True(rows.Max <= height); @@ -405,7 +399,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rect = new Rectangle(0, 0, width, height); - void RowAction(RowInterval rows, Memory memory) + void RowAction(RowInterval rows, Span memory) { } @@ -430,13 +424,13 @@ namespace SixLabors.ImageSharp.Tests.Helpers private readonly struct TestRowIntervalOperation : IRowIntervalOperation where TBuffer : unmanaged { - private readonly Action> action; + private readonly RowIntervalAction action; - public TestRowIntervalOperation(Action> action) + public TestRowIntervalOperation(RowIntervalAction action) => this.action = action; - public void Invoke(in RowInterval rows, Memory memory) - => this.action(rows, memory); + public void Invoke(in RowInterval rows, Span span) + => this.action(rows, span); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index d570b4d05..2ef62ed1c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -724,20 +724,19 @@ namespace SixLabors.ImageSharp.Tests this.source = source; } - public void Invoke(in RowInterval rows, Memory memory) + public void Invoke(in RowInterval rows, Span span) { - Span tempSpan = memory.Span; for (int y = rows.Min; y < rows.Max; y++) { Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, tempSpan, PixelConversionModifiers.Scale); - for (int i = 0; i < tempSpan.Length; i++) + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); + for (int i = 0; i < span.Length; i++) { - ref Vector4 v = ref tempSpan[i]; + ref Vector4 v = ref span[i]; v.W = 1F; } - PixelOperations.Instance.FromVector4Destructive(this.configuration, tempSpan, rowSpan, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); } } } From c5166c90da9ff8d1cb51a1e1205e3a3c8030f5ed Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Feb 2020 13:55:49 +1100 Subject: [PATCH 52/58] Update ProjectiveTransformProcessor{TPixel}.cs --- .../Transforms/ProjectiveTransformProcessor{TPixel}.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 83bc540eb..b241021aa 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -17,9 +17,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class ProjectiveTransformProcessor : TransformProcessor where TPixel : struct, IPixel { - private Size targetSize; + private readonly Size targetSize; private readonly IResampler resampler; - private Matrix4x4 transformMatrix; + private readonly Matrix4x4 transformMatrix; /// /// Initializes a new instance of the class. From 3cfe575054b6071cfa650eae16dcd0ad0a671394 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Feb 2020 23:02:19 +1100 Subject: [PATCH 53/58] Update EdgeDetectorCompassProcessor{TPixel}.cs --- .../EdgeDetectorCompassProcessor{TPixel}.cs | 63 ++++--------------- 1 file changed, 13 insertions(+), 50 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index fde669ea8..c4da1e4b0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -54,21 +53,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { DenseMatrix[] kernels = this.Kernels.Flatten(); - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; - - // 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); + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // We need a clean copy for each pass to start from using ImageFrame cleanCopy = source.Clone(); - using (var processor = new ConvolutionProcessor(this.Configuration, kernels[0], true, this.Source, this.SourceRectangle)) + using (var processor = new ConvolutionProcessor(this.Configuration, kernels[0], true, this.Source, interest)) { processor.Apply(source); } @@ -78,34 +68,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution return; } - int shiftY = startY; - int shiftX = startX; - - // Reset offset if necessary - if (minX > 0) - { - shiftX = 0; - } - - if (minY > 0) - { - shiftY = 0; - } - // Additional runs for (int i = 1; i < kernels.Length; i++) { using ImageFrame pass = cleanCopy.Clone(); - using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, this.SourceRectangle)) + using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, interest)) { processor.Apply(pass); } - var operation = new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX); + var operation = new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, interest); ParallelRowIterator.IterateRows( this.Configuration, - Rectangle.FromLTRB(minX, minY, maxX, maxY), + interest, in operation); } } @@ -119,24 +95,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly Buffer2D passPixels; private readonly int minX; private readonly int maxX; - private readonly int shiftY; - private readonly int shiftX; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( Buffer2D targetPixels, Buffer2D passPixels, - int minX, - int maxX, - int shiftY, - int shiftX) + Rectangle bounds) { this.targetPixels = targetPixels; this.passPixels = passPixels; - this.minX = minX; - this.maxX = maxX; - this.shiftY = shiftY; - this.shiftX = shiftX; + this.minX = bounds.X; + this.maxX = bounds.Right; } /// @@ -145,22 +114,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { for (int y = rows.Min; y < rows.Max; y++) { - int offsetY = y - this.shiftY; - - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(offsetY)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(offsetY)); + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(y)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(y)); for (int x = this.minX; x < this.maxX; x++) { - int offsetX = x - this.shiftX; - // Grab the max components of the two pixels - ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); - ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); + ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, x); + ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, x); - var pixelValue = Vector4.Max( - currentPassPixel.ToVector4(), - currentTargetPixel.ToVector4()); + var pixelValue = Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4()); currentTargetPixel.FromVector4(pixelValue); } From 49d02155b72c7af39c8ec54b8a11327e482f8ae1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Feb 2020 23:10:48 +1100 Subject: [PATCH 54/58] Update BinaryThresholdProcessor{TPixel}.cs --- .../BinaryThresholdProcessor{TPixel}.cs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 52be6abe2..ed14a44e9 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -42,18 +42,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization Configuration configuration = this.Configuration; var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - bool isAlphaOnly = typeof(TPixel) == typeof(A8); - var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - var operation = new RowIntervalOperation(source, upper, lower, threshold, startX, endX, isAlphaOnly); + var operation = new RowIntervalOperation(interest, source, upper, lower, threshold, isAlphaOnly); ParallelRowIterator.IterateRows( configuration, - workingRect, + interest, in operation); } @@ -66,26 +60,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization private readonly TPixel upper; private readonly TPixel lower; private readonly byte threshold; - private readonly int startX; - private readonly int endX; + private readonly int minX; + private readonly int maxX; private readonly bool isAlphaOnly; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( + Rectangle bounds, ImageFrame source, TPixel upper, TPixel lower, byte threshold, - int startX, - int endX, bool isAlphaOnly) { this.source = source; this.upper = upper; this.lower = lower; this.threshold = threshold; - this.startX = startX; - this.endX = endX; + this.minX = bounds.X; + this.maxX = bounds.Right; this.isAlphaOnly = isAlphaOnly; } @@ -98,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization { Span row = this.source.GetPixelRowSpan(y); - for (int x = this.startX; x < this.endX; x++) + for (int x = this.minX; x < this.maxX; x++) { ref TPixel color = ref row[x]; color.ToRgba32(ref rgba); From eefa57ed0d68013fdf8fdb2e5a829a36e36ee888 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Feb 2020 23:11:05 +1100 Subject: [PATCH 55/58] Cleanup --- src/ImageSharp/Advanced/ParallelRowIterator.cs | 12 ++++++------ .../Processors/Transforms/CropProcessor{TPixel}.cs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index e9d522966..5119190f3 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -65,14 +65,14 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(rectangle.Height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); - var rowAction = new WrappingRowIntervalOperation(in rowInfo, in operation); + var info = new WrappingRowIntervalInfo(top, bottom, verticalStep); + var wrappingOperation = new WrappingRowIntervalOperation(in info, in operation); Parallel.For( 0, numOfSteps, parallelOptions, - rowAction.Invoke); + wrappingOperation.Invoke); } /// @@ -133,14 +133,14 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); - var rowOperation = new WrappingRowIntervalBufferOperation(in rowInfo, allocator, in operation); + var info = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); + var wrappingOperation = new WrappingRowIntervalBufferOperation(in info, allocator, in operation); Parallel.For( 0, numOfSteps, parallelOptions, - rowOperation.Invoke); + wrappingOperation.Invoke); } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index d8c77e8e7..4fd35d375 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -51,12 +51,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - var rowAction = new RowIntervalOperation(ref bounds, source, destination); + var operation = new RowIntervalOperation(ref bounds, source, destination); ParallelRowIterator.IterateRows( bounds, in parallelSettings, - in rowAction); + in operation); } /// From 4b7718e3c4ae8e82f4b651ddc9a3f88630110c9e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 12 Feb 2020 11:03:01 +1100 Subject: [PATCH 56/58] Undo unrelated changes and clean up. --- .../Advanced/ParallelRowIterator.Wrappers.cs | 4 +-- .../Advanced/ParallelRowIterator.cs | 4 +-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 10 ++----- .../PixelImplementations/Rgba64.cs | 12 ++++---- .../Utils/Vector4Converters.RgbaCompatible.cs | 3 +- src/ImageSharp/Primitives/Rectangle.cs | 30 +++++++++---------- .../Convolution/BokehBlurProcessor{TPixel}.cs | 8 ++--- ...lHistogramEqualizationProcessor{TPixel}.cs | 14 ++++----- .../Transforms/CropProcessor{TPixel}.cs | 4 +-- .../ProjectiveTransformProcessor{TPixel}.cs | 4 +-- .../Transforms/Resize/ResizeKernelMap.cs | 2 +- 11 files changed, 44 insertions(+), 51 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs index 4abcd1a3e..9413cf467 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Advanced var rows = new RowInterval(yMin, yMax); // Skip the safety copy when invoking a potentially impure method on a readonly field - Unsafe.AsRef(this.operation).Invoke(in rows); + Unsafe.AsRef(in this.operation).Invoke(in rows); } } @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Advanced using IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX); - Unsafe.AsRef(this.operation).Invoke(in rows, buffer.Memory.Span); + Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span); } } } diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 5119190f3..b2e7f523f 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Advanced if (numOfSteps == 1) { var rows = new RowInterval(top, bottom); - Unsafe.AsRef(operation).Invoke(in rows); + Unsafe.AsRef(in operation).Invoke(in rows); return; } @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Advanced var rows = new RowInterval(top, bottom); using (IMemoryOwner buffer = allocator.Allocate(width)) { - Unsafe.AsRef(operation).Invoke(rows, buffer.Memory.Span); + Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span); } return; diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index bcbfda2ba..69a80e024 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -554,14 +554,8 @@ namespace SixLabors.ImageSharp.Formats.Png return; } - /* Grab the palette and write it to the stream. - * Here the palette is reinterpreted as a mutable Memory value, - * which is possible because the two memory types have the same layout. - * This is done so that the Span we're working on is mutable, - * so that we can skip the safety copies done by the compiler when we - * invoke the IPixel.ToRgba32 method below, which is not marked as readonly. */ - ReadOnlyMemory paletteMemory = quantized.Palette; - Span palette = Unsafe.As, Memory>(ref paletteMemory).Span; + // Grab the palette and write it to the stream. + ReadOnlySpan palette = quantized.Palette.Span; int paletteLength = Math.Min(palette.Length, 256); int colorTableLength = paletteLength * 3; bool anyAlpha = false; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index d71f7bca4..56bc6f455 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; + public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -343,7 +343,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public readonly Rgba32 ToRgba32() + public Rgba32 ToRgba32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -357,7 +357,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public readonly Bgra32 ToBgra32() + public Bgra32 ToBgra32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -371,7 +371,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public readonly Argb32 ToArgb32() + public Argb32 ToArgb32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -385,7 +385,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public readonly Rgb24 ToRgb24() + public Rgb24 ToRgb24() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -398,7 +398,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public readonly Bgr24 ToBgr24() + public Bgr24 ToBgr24() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 79574e442..9c3a592d7 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -66,8 +66,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils MemoryMarshal.Cast(lastQuarterOfDestBuffer), MemoryMarshal.Cast(destVectors.Slice(0, countWithoutLastItem))); - // Reinterpret as a mutable reference to skip the safety copy of the readonly value - destVectors[countWithoutLastItem] = Unsafe.AsRef(sourcePixels[countWithoutLastItem]).ToVector4(); + destVectors[countWithoutLastItem] = sourcePixels[countWithoutLastItem].ToVector4(); // TODO: Investigate optimized 1-pass approach! ApplyForwardConversionModifiers(destVectors, modifiers); diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs index 5b2e9411c..d391057a9 100644 --- a/src/ImageSharp/Primitives/Rectangle.cs +++ b/src/ImageSharp/Primitives/Rectangle.cs @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp public Point Location { [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => new Point(this.X, this.Y); + get => new Point(this.X, this.Y); [MethodImpl(MethodImplOptions.AggressiveInlining)] set @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp public Size Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => new Size(this.Width, this.Height); + get => new Size(this.Width, this.Height); [MethodImpl(MethodImplOptions.AggressiveInlining)] set @@ -112,17 +112,17 @@ namespace SixLabors.ImageSharp /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] - public readonly bool IsEmpty => this.Equals(Empty); + public bool IsEmpty => this.Equals(Empty); /// /// Gets the y-coordinate of the top edge of this . /// - public readonly int Top => this.Y; + public int Top => this.Y; /// /// Gets the x-coordinate of the right edge of this . /// - public readonly int Right + public int Right { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => unchecked(this.X + this.Width); @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp /// /// Gets the y-coordinate of the bottom edge of this . /// - public readonly int Bottom + public int Bottom { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => unchecked(this.Y + this.Height); @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp /// /// Gets the x-coordinate of the left edge of this . /// - public readonly int Left => this.X; + public int Left => this.X; /// /// Creates a with the coordinates of the specified . @@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp /// The out value for Y. /// The out value for the width. /// The out value for the height. - public readonly void Deconstruct(out int x, out int y, out int width, out int height) + public void Deconstruct(out int x, out int y, out int width, out int height) { x = this.X; y = this.Y; @@ -383,7 +383,7 @@ namespace SixLabors.ImageSharp /// The y-coordinate of the given point. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; + public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; /// /// Determines if the specified point is contained within the rectangular region defined by this . @@ -391,7 +391,7 @@ namespace SixLabors.ImageSharp /// The point. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Contains(Point point) => this.Contains(point.X, point.Y); + public bool Contains(Point point) => this.Contains(point.X, point.Y); /// /// Determines if the rectangular region represented by is entirely contained @@ -400,7 +400,7 @@ namespace SixLabors.ImageSharp /// The rectangle. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Contains(Rectangle rectangle) => + public bool Contains(Rectangle rectangle) => (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); @@ -411,7 +411,7 @@ namespace SixLabors.ImageSharp /// The other Rectange. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IntersectsWith(Rectangle rectangle) => + public bool IntersectsWith(Rectangle rectangle) => (rectangle.X < this.Right) && (this.X < rectangle.Right) && (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); @@ -438,13 +438,13 @@ namespace SixLabors.ImageSharp } /// - public override readonly int GetHashCode() + public override int GetHashCode() { return HashCode.Combine(this.X, this.Y, this.Width, this.Height); } /// - public override readonly string ToString() + public override string ToString() { return $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; } @@ -454,7 +454,7 @@ namespace SixLabors.ImageSharp /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Equals(Rectangle other) => + public bool Equals(Rectangle other) => this.X.Equals(other.X) && this.Y.Equals(other.Y) && this.Width.Equals(other.Width) && diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index e388fdaad..1ebd6476e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -316,14 +316,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Vector4 parameters = Unsafe.Add(ref paramsRef, i); // Compute the vertical 1D convolution - var verticalOperation = new ApplyVerticalConvolutionRowIntervalOperation(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel); + var verticalOperation = new ApplyVerticalConvolutionRowIntervalOperation(sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel); ParallelRowIterator.IterateRows( configuration, sourceRectangle, in verticalOperation); // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer - var horizontalOperation = new ApplyHorizontalConvolutionRowIntervalOperation(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W); + var horizontalOperation = new ApplyHorizontalConvolutionRowIntervalOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W); ParallelRowIterator.IterateRows( configuration, sourceRectangle, @@ -345,7 +345,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public ApplyVerticalConvolutionRowIntervalOperation( - ref Rectangle bounds, + Rectangle bounds, Buffer2D targetValues, Buffer2D sourcePixels, Complex64[] kernel) @@ -390,7 +390,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public ApplyHorizontalConvolutionRowIntervalOperation( - ref Rectangle bounds, + Rectangle bounds, Buffer2D targetValues, Buffer2D sourceValues, Complex64[] kernel, diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index d7ea80737..07fa55c5d 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -47,15 +47,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; int numberOfPixels = source.Width * source.Height; - var workingRect = new Rectangle(0, 0, source.Width, source.Height); + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); // Build the histogram of the grayscale levels - var grayscaleOperation = new GrayscaleLevelsRowIntervalOperation(workingRect, histogramBuffer, source, this.LuminanceLevels); + var grayscaleOperation = new GrayscaleLevelsRowIntervalOperation(interest, histogramBuffer, source, this.LuminanceLevels); ParallelRowIterator.IterateRows( this.Configuration, - workingRect, + interest, in grayscaleOperation); Span histogram = histogramBuffer.GetSpan(); @@ -75,10 +75,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; // Apply the cdf to each pixel of the image - var cdfOperation = new CdfApplicationRowIntervalOperation(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); + var cdfOperation = new CdfApplicationRowIntervalOperation(interest, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); ParallelRowIterator.IterateRows( this.Configuration, - workingRect, + interest, in cdfOperation); } @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization [MethodImpl(InliningOptions.ShortMethod)] public GrayscaleLevelsRowIntervalOperation( - in Rectangle bounds, + Rectangle bounds, IMemoryOwner histogramBuffer, ImageFrame source, int luminanceLevels) @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization [MethodImpl(InliningOptions.ShortMethod)] public CdfApplicationRowIntervalOperation( - in Rectangle bounds, + Rectangle bounds, IMemoryOwner cdfBuffer, ImageFrame source, int luminanceLevels, diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 4fd35d375..6ad7aa2a2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - var operation = new RowIntervalOperation(ref bounds, source, destination); + var operation = new RowIntervalOperation(bounds, source, destination); ParallelRowIterator.IterateRows( bounds, @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The source for the current instance. /// The destination for the current instance. [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation(ref Rectangle bounds, ImageFrame source, ImageFrame destination) + public RowIntervalOperation(Rectangle bounds, ImageFrame source, ImageFrame destination) { this.bounds = bounds; this.source = source; diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index b241021aa..da071e3f2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Rectangle sourceBounds = this.SourceRectangle; - var nnOperation = new NearestNeighborRowIntervalOperation(ref sourceBounds, ref matrix, width, source, destination); + var nnOperation = new NearestNeighborRowIntervalOperation(sourceBounds, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( configuration, targetBounds, @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public NearestNeighborRowIntervalOperation( - ref Rectangle bounds, + Rectangle bounds, ref Matrix4x4 matrix, int maxX, ImageFrame source, diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index d3d59a594..8e8eaceb0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public void Dispose() { - Unsafe.AsRef(this.pinHandle).Dispose(); + this.pinHandle.Dispose(); this.data.Dispose(); } From b5d055f7647da8bd95432284d51aa80422493a22 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 12 Feb 2020 11:27:03 +1100 Subject: [PATCH 57/58] Update QuantizeProcessor{TPixel}.cs --- .../Quantization/QuantizeProcessor{TPixel}.cs | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index 5e732982c..276919d60 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -2,8 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization @@ -35,27 +36,50 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization protected override void OnFrameApply(ImageFrame source) { Configuration configuration = this.Configuration; - using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(configuration)) - using (IQuantizedFrame quantized = frameQuantizer.QuantizeFrame(source)) - { - int paletteCount = quantized.Palette.Length - 1; + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(configuration); + using IQuantizedFrame quantized = frameQuantizer.QuantizeFrame(source); - // Not parallel to remove "quantized" closure allocation. - // We can operate directly on the source here as we've already read it to get the - // quantized result - for (int y = 0; y < source.Height; y++) - { - Span row = source.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = quantized.GetPixelSpan(); + var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); + ParallelRowIterator.IterateRows( + configuration, + this.SourceRectangle, + in operation); + } - ReadOnlySpan paletteSpan = quantized.Palette.Span; + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly ImageFrame source; + private readonly IQuantizedFrame quantized; + private readonly int maxPaletteIndex; - int yy = y * source.Width; + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Rectangle bounds, + ImageFrame source, + IQuantizedFrame quantized) + { + this.bounds = bounds; + this.source = source; + this.quantized = quantized; + this.maxPaletteIndex = quantized.Palette.Length - 1; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ReadOnlySpan quantizedPixelSpan = this.quantized.GetPixelSpan(); + ReadOnlySpan paletteSpan = this.quantized.Palette.Span; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span row = this.source.GetPixelRowSpan(y); + int yy = y * this.bounds.Width; - for (int x = 0; x < source.Width; x++) + for (int x = this.bounds.X; x < this.bounds.Right; x++) { int i = x + yy; - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; + row[x] = paletteSpan[Math.Min(this.maxPaletteIndex, quantizedPixelSpan[i])]; } } } From c66dd0c93cee872be5725b4bee1b8705ab06c58c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 12 Feb 2020 11:31:53 +1100 Subject: [PATCH 58/58] Rename things --- .../Advanced/ParallelRowIterator.Wrappers.cs | 26 +++++++++---------- .../Advanced/ParallelRowIterator.cs | 8 +++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs index 9413cf467..adbad0d66 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs @@ -17,35 +17,35 @@ namespace SixLabors.ImageSharp.Advanced /// public static partial class ParallelRowIterator { - private readonly struct WrappingRowIntervalInfo + private readonly struct IterationParameters { public readonly int MinY; public readonly int MaxY; public readonly int StepY; - public readonly int MaxX; + public readonly int Width; - public WrappingRowIntervalInfo(int minY, int maxY, int stepY) + public IterationParameters(int minY, int maxY, int stepY) : this(minY, maxY, stepY, 0) { } - public WrappingRowIntervalInfo(int minY, int maxY, int stepY, int maxX) + public IterationParameters(int minY, int maxY, int stepY, int width) { this.MinY = minY; this.MaxY = maxY; this.StepY = stepY; - this.MaxX = maxX; + this.Width = width; } } - private readonly struct WrappingRowIntervalOperation + private readonly struct RowIntervalOperationWrapper where T : struct, IRowIntervalOperation { - private readonly WrappingRowIntervalInfo info; + private readonly IterationParameters info; private readonly T operation; [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowIntervalOperation(in WrappingRowIntervalInfo info, in T operation) + public RowIntervalOperationWrapper(in IterationParameters info, in T operation) { this.info = info; this.operation = operation; @@ -69,17 +69,17 @@ namespace SixLabors.ImageSharp.Advanced } } - private readonly struct WrappingRowIntervalBufferOperation + private readonly struct RowIntervalOperationWrapper where T : struct, IRowIntervalOperation where TBuffer : unmanaged { - private readonly WrappingRowIntervalInfo info; + private readonly IterationParameters info; private readonly MemoryAllocator allocator; private readonly T operation; [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowIntervalBufferOperation( - in WrappingRowIntervalInfo info, + public RowIntervalOperationWrapper( + in IterationParameters info, MemoryAllocator allocator, in T operation) { @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Advanced int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); var rows = new RowInterval(yMin, yMax); - using IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX); + using IMemoryOwner buffer = this.allocator.Allocate(this.info.Width); Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span); } diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index b2e7f523f..123784c57 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -65,8 +65,8 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(rectangle.Height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var info = new WrappingRowIntervalInfo(top, bottom, verticalStep); - var wrappingOperation = new WrappingRowIntervalOperation(in info, in operation); + var info = new IterationParameters(top, bottom, verticalStep); + var wrappingOperation = new RowIntervalOperationWrapper(in info, in operation); Parallel.For( 0, @@ -133,8 +133,8 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var info = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); - var wrappingOperation = new WrappingRowIntervalBufferOperation(in info, allocator, in operation); + var info = new IterationParameters(top, bottom, verticalStep, width); + var wrappingOperation = new RowIntervalOperationWrapper(in info, allocator, in operation); Parallel.For( 0,