diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation.cs b/src/ImageSharp/Advanced/IRowIntervalOperation.cs new file mode 100644 index 0000000000..3e1b086218 --- /dev/null +++ b/src/ImageSharp/Advanced/IRowIntervalOperation.cs @@ -0,0 +1,21 @@ +// 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 +{ + /// + /// Defines the contract for an action that operates on a row interval. + /// + public interface IRowIntervalOperation + { + /// + /// Invokes the method passing the row interval. + /// + /// The row interval. + void Invoke(in RowInterval rows); + } +} diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs new file mode 100644 index 0000000000..c18842a92c --- /dev/null +++ b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs @@ -0,0 +1,25 @@ +// 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 interval with a temporary buffer. + /// + /// The type of buffer elements. + public interface IRowIntervalOperation + 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, Span span); + } +} 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 f17d70a2a0..54ee069184 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/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs new file mode 100644 index 0000000000..adbad0d662 --- /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 IterationParameters + { + public readonly int MinY; + public readonly int MaxY; + public readonly int StepY; + public readonly int Width; + + public IterationParameters(int minY, int maxY, int stepY) + : this(minY, maxY, stepY, 0) + { + } + + public IterationParameters(int minY, int maxY, int stepY, int width) + { + this.MinY = minY; + this.MaxY = maxY; + this.StepY = stepY; + this.Width = width; + } + } + + private readonly struct RowIntervalOperationWrapper + where T : struct, IRowIntervalOperation + { + private readonly IterationParameters info; + private readonly T operation; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperationWrapper(in IterationParameters 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(in this.operation).Invoke(in rows); + } + } + + private readonly struct RowIntervalOperationWrapper + where T : struct, IRowIntervalOperation + where TBuffer : unmanaged + { + private readonly IterationParameters info; + private readonly MemoryAllocator allocator; + private readonly T operation; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperationWrapper( + in IterationParameters 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.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 new file mode 100644 index 0000000000..123784c57f --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -0,0 +1,162 @@ +// 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 + { + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + /// The type of row operation to perform. + /// The to get the parallel settings from. + /// The . + /// The operation defining the iteration logic on a single . + [MethodImpl(InliningOptions.ShortMethod)] + 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 operation); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + /// The type of row operation to perform. + /// The . + /// The . + /// The operation defining the iteration logic on a single . + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowIntervalOperation + { + 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(in operation).Invoke(in rows); + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var info = new IterationParameters(top, bottom, verticalStep); + var wrappingOperation = new RowIntervalOperationWrapper(in info, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.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 operation to perform. + /// The type of buffer elements. + /// The to get the parallel settings from. + /// 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 + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + 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. + /// + /// The type of row operation to perform. + /// The type of buffer elements. + /// The . + /// The . + /// The operation defining the iteration logic on a single . + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowIntervalOperation + 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(operation).Invoke(in rows, buffer.Memory.Span); + } + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var info = new IterationParameters(top, bottom, verticalStep, width); + var wrappingOperation = new RowIntervalOperationWrapper(in info, allocator, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); + + private static void ValidateRectangle(Rectangle rectangle) + { + Guard.MustBeGreaterThan( + rectangle.Width, + 0, + $"{nameof(rectangle)}.{nameof(rectangle.Width)}"); + + Guard.MustBeGreaterThan( + rectangle.Height, + 0, + $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs deleted file mode 100644 index ecefadb08d..0000000000 --- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs +++ /dev/null @@ -1,160 +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 System.Threading.Tasks; - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Advanced.ParallelUtils -{ - /// - /// 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 class ParallelHelper - { - /// - /// 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 . - public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) - { - var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - - IterateRows(rectangle, 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 . - public static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action body) - { - 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(rows); - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - i => - { - int yMin = rectangle.Top + (i * verticalStep); - int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); - - var rows = new RowInterval(yMin, yMax); - 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 IterateRowsWithTempBuffer( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action> body) - where T : unmanaged - { - ValidateRectangle(rectangle); - - int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); - - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - - MemoryAllocator memoryAllocator = 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)) - { - body(rows, buffer.Memory); - } - - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - i => - { - int yMin = rectangle.Top + (i * verticalStep); - 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. - /// - internal static void IterateRowsWithTempBuffer( - Rectangle rectangle, - Configuration configuration, - Action> body) - where T : unmanaged - { - IterateRowsWithTempBuffer(rectangle, ParallelExecutionSettings.FromConfiguration(configuration), body); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); - - private static void ValidateRectangle(Rectangle rectangle) - { - Guard.MustBeGreaterThan( - rectangle.Width, - 0, - $"{nameof(rectangle)}.{nameof(rectangle.Width)}"); - - Guard.MustBeGreaterThan( - rectangle.Height, - 0, - $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); - } - } -} diff --git a/src/ImageSharp/Color/Color.NamedColors.cs b/src/ImageSharp/Color/Color.NamedColors.cs index 0575a3e99e..8eb3fbcaf7 100644 --- a/src/ImageSharp/Color/Color.NamedColors.cs +++ b/src/ImageSharp/Color/Color.NamedColors.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; + namespace SixLabors.ImageSharp { /// @@ -8,6 +11,8 @@ namespace SixLabors.ImageSharp /// public readonly partial struct Color { + private static readonly Lazy> NamedColorsLookupLazy = new Lazy>(CreateNamedColorsLookup, true); + /// /// Represents a matching the W3C definition that has an hex value of #F0F8FF. /// @@ -111,7 +116,7 @@ namespace SixLabors.ImageSharp /// /// Represents a matching the W3C definition that has an hex value of #00FFFF. /// - public static readonly Color Cyan = FromRgba(0, 255, 255, 255); + public static readonly Color Cyan = Aqua; /// /// Represents a matching the W3C definition that has an hex value of #00008B. @@ -138,6 +143,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color DarkGreen = FromRgba(0, 100, 0, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. + /// + public static readonly Color DarkGrey = DarkGray; + /// /// Represents a matching the W3C definition that has an hex value of #BDB76B. /// @@ -188,6 +198,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color DarkSlateGray = FromRgba(47, 79, 79, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. + /// + public static readonly Color DarkSlateGrey = DarkSlateGray; + /// /// Represents a matching the W3C definition that has an hex value of #00CED1. /// @@ -213,6 +228,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color DimGray = FromRgba(105, 105, 105, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #696969. + /// + public static readonly Color DimGrey = DimGray; + /// /// Represents a matching the W3C definition that has an hex value of #1E90FF. /// @@ -273,6 +293,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color GreenYellow = FromRgba(173, 255, 47, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly Color Grey = Gray; + /// /// Represents a matching the W3C definition that has an hex value of #F0FFF0. /// @@ -353,6 +378,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color LightGreen = FromRgba(144, 238, 144, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. + /// + public static readonly Color LightGrey = LightGray; + /// /// Represents a matching the W3C definition that has an hex value of #FFB6C1. /// @@ -378,6 +408,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color LightSlateGray = FromRgba(119, 136, 153, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #778899. + /// + public static readonly Color LightSlateGrey = LightSlateGray; + /// /// Represents a matching the W3C definition that has an hex value of #B0C4DE. /// @@ -406,7 +441,7 @@ namespace SixLabors.ImageSharp /// /// Represents a matching the W3C definition that has an hex value of #FF00FF. /// - public static readonly Color Magenta = FromRgba(255, 0, 255, 255); + public static readonly Color Magenta = Fuchsia; /// /// Represents a matching the W3C definition that has an hex value of #800000. @@ -643,6 +678,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color SlateGray = FromRgba(112, 128, 144, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #708090. + /// + public static readonly Color SlateGrey = SlateGray; + /// /// Represents a matching the W3C definition that has an hex value of #FFFAFA. /// @@ -717,5 +757,161 @@ namespace SixLabors.ImageSharp /// Represents a matching the W3C definition that has an hex value of #9ACD32. /// public static readonly Color YellowGreen = FromRgba(154, 205, 50, 255); + + private static Dictionary CreateNamedColorsLookup() + { + return new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { nameof(AliceBlue), AliceBlue }, + { nameof(AntiqueWhite), AntiqueWhite }, + { nameof(Aqua), Aqua }, + { nameof(Aquamarine), Aquamarine }, + { nameof(Azure), Azure }, + { nameof(Beige), Beige }, + { nameof(Bisque), Bisque }, + { nameof(Black), Black }, + { nameof(BlanchedAlmond), BlanchedAlmond }, + { nameof(Blue), Blue }, + { nameof(BlueViolet), BlueViolet }, + { nameof(Brown), Brown }, + { nameof(BurlyWood), BurlyWood }, + { nameof(CadetBlue), CadetBlue }, + { nameof(Chartreuse), Chartreuse }, + { nameof(Chocolate), Chocolate }, + { nameof(Coral), Coral }, + { nameof(CornflowerBlue), CornflowerBlue }, + { nameof(Cornsilk), Cornsilk }, + { nameof(Crimson), Crimson }, + { nameof(Cyan), Cyan }, + { nameof(DarkBlue), DarkBlue }, + { nameof(DarkCyan), DarkCyan }, + { nameof(DarkGoldenrod), DarkGoldenrod }, + { nameof(DarkGray), DarkGray }, + { nameof(DarkGreen), DarkGreen }, + { nameof(DarkGrey), DarkGrey }, + { nameof(DarkKhaki), DarkKhaki }, + { nameof(DarkMagenta), DarkMagenta }, + { nameof(DarkOliveGreen), DarkOliveGreen }, + { nameof(DarkOrange), DarkOrange }, + { nameof(DarkOrchid), DarkOrchid }, + { nameof(DarkRed), DarkRed }, + { nameof(DarkSalmon), DarkSalmon }, + { nameof(DarkSeaGreen), DarkSeaGreen }, + { nameof(DarkSlateBlue), DarkSlateBlue }, + { nameof(DarkSlateGray), DarkSlateGray }, + { nameof(DarkSlateGrey), DarkSlateGrey }, + { nameof(DarkTurquoise), DarkTurquoise }, + { nameof(DarkViolet), DarkViolet }, + { nameof(DeepPink), DeepPink }, + { nameof(DeepSkyBlue), DeepSkyBlue }, + { nameof(DimGray), DimGray }, + { nameof(DimGrey), DimGrey }, + { nameof(DodgerBlue), DodgerBlue }, + { nameof(Firebrick), Firebrick }, + { nameof(FloralWhite), FloralWhite }, + { nameof(ForestGreen), ForestGreen }, + { nameof(Fuchsia), Fuchsia }, + { nameof(Gainsboro), Gainsboro }, + { nameof(GhostWhite), GhostWhite }, + { nameof(Gold), Gold }, + { nameof(Goldenrod), Goldenrod }, + { nameof(Gray), Gray }, + { nameof(Green), Green }, + { nameof(GreenYellow), GreenYellow }, + { nameof(Grey), Grey }, + { nameof(Honeydew), Honeydew }, + { nameof(HotPink), HotPink }, + { nameof(IndianRed), IndianRed }, + { nameof(Indigo), Indigo }, + { nameof(Ivory), Ivory }, + { nameof(Khaki), Khaki }, + { nameof(Lavender), Lavender }, + { nameof(LavenderBlush), LavenderBlush }, + { nameof(LawnGreen), LawnGreen }, + { nameof(LemonChiffon), LemonChiffon }, + { nameof(LightBlue), LightBlue }, + { nameof(LightCoral), LightCoral }, + { nameof(LightCyan), LightCyan }, + { nameof(LightGoldenrodYellow), LightGoldenrodYellow }, + { nameof(LightGray), LightGray }, + { nameof(LightGreen), LightGreen }, + { nameof(LightGrey), LightGrey }, + { nameof(LightPink), LightPink }, + { nameof(LightSalmon), LightSalmon }, + { nameof(LightSeaGreen), LightSeaGreen }, + { nameof(LightSkyBlue), LightSkyBlue }, + { nameof(LightSlateGray), LightSlateGray }, + { nameof(LightSlateGrey), LightSlateGrey }, + { nameof(LightSteelBlue), LightSteelBlue }, + { nameof(LightYellow), LightYellow }, + { nameof(Lime), Lime }, + { nameof(LimeGreen), LimeGreen }, + { nameof(Linen), Linen }, + { nameof(Magenta), Magenta }, + { nameof(Maroon), Maroon }, + { nameof(MediumAquamarine), MediumAquamarine }, + { nameof(MediumBlue), MediumBlue }, + { nameof(MediumOrchid), MediumOrchid }, + { nameof(MediumPurple), MediumPurple }, + { nameof(MediumSeaGreen), MediumSeaGreen }, + { nameof(MediumSlateBlue), MediumSlateBlue }, + { nameof(MediumSpringGreen), MediumSpringGreen }, + { nameof(MediumTurquoise), MediumTurquoise }, + { nameof(MediumVioletRed), MediumVioletRed }, + { nameof(MidnightBlue), MidnightBlue }, + { nameof(MintCream), MintCream }, + { nameof(MistyRose), MistyRose }, + { nameof(Moccasin), Moccasin }, + { nameof(NavajoWhite), NavajoWhite }, + { nameof(Navy), Navy }, + { nameof(OldLace), OldLace }, + { nameof(Olive), Olive }, + { nameof(OliveDrab), OliveDrab }, + { nameof(Orange), Orange }, + { nameof(OrangeRed), OrangeRed }, + { nameof(Orchid), Orchid }, + { nameof(PaleGoldenrod), PaleGoldenrod }, + { nameof(PaleGreen), PaleGreen }, + { nameof(PaleTurquoise), PaleTurquoise }, + { nameof(PaleVioletRed), PaleVioletRed }, + { nameof(PapayaWhip), PapayaWhip }, + { nameof(PeachPuff), PeachPuff }, + { nameof(Peru), Peru }, + { nameof(Pink), Pink }, + { nameof(Plum), Plum }, + { nameof(PowderBlue), PowderBlue }, + { nameof(Purple), Purple }, + { nameof(RebeccaPurple), RebeccaPurple }, + { nameof(Red), Red }, + { nameof(RosyBrown), RosyBrown }, + { nameof(RoyalBlue), RoyalBlue }, + { nameof(SaddleBrown), SaddleBrown }, + { nameof(Salmon), Salmon }, + { nameof(SandyBrown), SandyBrown }, + { nameof(SeaGreen), SeaGreen }, + { nameof(SeaShell), SeaShell }, + { nameof(Sienna), Sienna }, + { nameof(Silver), Silver }, + { nameof(SkyBlue), SkyBlue }, + { nameof(SlateBlue), SlateBlue }, + { nameof(SlateGray), SlateGray }, + { nameof(SlateGrey), SlateGrey }, + { nameof(Snow), Snow }, + { nameof(SpringGreen), SpringGreen }, + { nameof(SteelBlue), SteelBlue }, + { nameof(Tan), Tan }, + { nameof(Teal), Teal }, + { nameof(Thistle), Thistle }, + { nameof(Tomato), Tomato }, + { nameof(Transparent), Transparent }, + { nameof(Turquoise), Turquoise }, + { nameof(Violet), Violet }, + { nameof(Wheat), Wheat }, + { nameof(White), White }, + { nameof(WhiteSmoke), WhiteSmoke }, + { nameof(Yellow), Yellow }, + { nameof(YellowGreen), YellowGreen } + }; + } } } diff --git a/src/ImageSharp/Color/Color.WernerPalette.cs b/src/ImageSharp/Color/Color.WernerPalette.cs index 768fe065cd..2948b4c52c 100644 --- a/src/ImageSharp/Color/Color.WernerPalette.cs +++ b/src/ImageSharp/Color/Color.WernerPalette.cs @@ -20,116 +20,116 @@ namespace SixLabors.ImageSharp private static Color[] CreateWernerPalette() => new[] { - FromHex("#f1e9cd"), - FromHex("#f2e7cf"), - FromHex("#ece6d0"), - FromHex("#f2eacc"), - FromHex("#f3e9ca"), - FromHex("#f2ebcd"), - FromHex("#e6e1c9"), - FromHex("#e2ddc6"), - FromHex("#cbc8b7"), - FromHex("#bfbbb0"), - FromHex("#bebeb3"), - FromHex("#b7b5ac"), - FromHex("#bab191"), - FromHex("#9c9d9a"), - FromHex("#8a8d84"), - FromHex("#5b5c61"), - FromHex("#555152"), - FromHex("#413f44"), - FromHex("#454445"), - FromHex("#423937"), - FromHex("#433635"), - FromHex("#252024"), - FromHex("#241f20"), - FromHex("#281f3f"), - FromHex("#1c1949"), - FromHex("#4f638d"), - FromHex("#383867"), - FromHex("#5c6b8f"), - FromHex("#657abb"), - FromHex("#6f88af"), - FromHex("#7994b5"), - FromHex("#6fb5a8"), - FromHex("#719ba2"), - FromHex("#8aa1a6"), - FromHex("#d0d5d3"), - FromHex("#8590ae"), - FromHex("#3a2f52"), - FromHex("#39334a"), - FromHex("#6c6d94"), - FromHex("#584c77"), - FromHex("#533552"), - FromHex("#463759"), - FromHex("#bfbac0"), - FromHex("#77747f"), - FromHex("#4a475c"), - FromHex("#b8bfaf"), - FromHex("#b2b599"), - FromHex("#979c84"), - FromHex("#5d6161"), - FromHex("#61ac86"), - FromHex("#a4b6a7"), - FromHex("#adba98"), - FromHex("#93b778"), - FromHex("#7d8c55"), - FromHex("#33431e"), - FromHex("#7c8635"), - FromHex("#8e9849"), - FromHex("#c2c190"), - FromHex("#67765b"), - FromHex("#ab924b"), - FromHex("#c8c76f"), - FromHex("#ccc050"), - FromHex("#ebdd99"), - FromHex("#ab9649"), - FromHex("#dbc364"), - FromHex("#e6d058"), - FromHex("#ead665"), - FromHex("#d09b2c"), - FromHex("#a36629"), - FromHex("#a77d35"), - FromHex("#f0d696"), - FromHex("#d7c485"), - FromHex("#f1d28c"), - FromHex("#efcc83"), - FromHex("#f3daa7"), - FromHex("#dfa837"), - FromHex("#ebbc71"), - FromHex("#d17c3f"), - FromHex("#92462f"), - FromHex("#be7249"), - FromHex("#bb603c"), - FromHex("#c76b4a"), - FromHex("#a75536"), - FromHex("#b63e36"), - FromHex("#b5493a"), - FromHex("#cd6d57"), - FromHex("#711518"), - FromHex("#e9c49d"), - FromHex("#eedac3"), - FromHex("#eecfbf"), - FromHex("#ce536b"), - FromHex("#b74a70"), - FromHex("#b7757c"), - FromHex("#612741"), - FromHex("#7a4848"), - FromHex("#3f3033"), - FromHex("#8d746f"), - FromHex("#4d3635"), - FromHex("#6e3b31"), - FromHex("#864735"), - FromHex("#553d3a"), - FromHex("#613936"), - FromHex("#7a4b3a"), - FromHex("#946943"), - FromHex("#c39e6d"), - FromHex("#513e32"), - FromHex("#8b7859"), - FromHex("#9b856b"), - FromHex("#766051"), - FromHex("#453b32") + ParseHex("#f1e9cd"), + ParseHex("#f2e7cf"), + ParseHex("#ece6d0"), + ParseHex("#f2eacc"), + ParseHex("#f3e9ca"), + ParseHex("#f2ebcd"), + ParseHex("#e6e1c9"), + ParseHex("#e2ddc6"), + ParseHex("#cbc8b7"), + ParseHex("#bfbbb0"), + ParseHex("#bebeb3"), + ParseHex("#b7b5ac"), + ParseHex("#bab191"), + ParseHex("#9c9d9a"), + ParseHex("#8a8d84"), + ParseHex("#5b5c61"), + ParseHex("#555152"), + ParseHex("#413f44"), + ParseHex("#454445"), + ParseHex("#423937"), + ParseHex("#433635"), + ParseHex("#252024"), + ParseHex("#241f20"), + ParseHex("#281f3f"), + ParseHex("#1c1949"), + ParseHex("#4f638d"), + ParseHex("#383867"), + ParseHex("#5c6b8f"), + ParseHex("#657abb"), + ParseHex("#6f88af"), + ParseHex("#7994b5"), + ParseHex("#6fb5a8"), + ParseHex("#719ba2"), + ParseHex("#8aa1a6"), + ParseHex("#d0d5d3"), + ParseHex("#8590ae"), + ParseHex("#3a2f52"), + ParseHex("#39334a"), + ParseHex("#6c6d94"), + ParseHex("#584c77"), + ParseHex("#533552"), + ParseHex("#463759"), + ParseHex("#bfbac0"), + ParseHex("#77747f"), + ParseHex("#4a475c"), + ParseHex("#b8bfaf"), + ParseHex("#b2b599"), + ParseHex("#979c84"), + ParseHex("#5d6161"), + ParseHex("#61ac86"), + ParseHex("#a4b6a7"), + ParseHex("#adba98"), + ParseHex("#93b778"), + ParseHex("#7d8c55"), + ParseHex("#33431e"), + ParseHex("#7c8635"), + ParseHex("#8e9849"), + ParseHex("#c2c190"), + ParseHex("#67765b"), + ParseHex("#ab924b"), + ParseHex("#c8c76f"), + ParseHex("#ccc050"), + ParseHex("#ebdd99"), + ParseHex("#ab9649"), + ParseHex("#dbc364"), + ParseHex("#e6d058"), + ParseHex("#ead665"), + ParseHex("#d09b2c"), + ParseHex("#a36629"), + ParseHex("#a77d35"), + ParseHex("#f0d696"), + ParseHex("#d7c485"), + ParseHex("#f1d28c"), + ParseHex("#efcc83"), + ParseHex("#f3daa7"), + ParseHex("#dfa837"), + ParseHex("#ebbc71"), + ParseHex("#d17c3f"), + ParseHex("#92462f"), + ParseHex("#be7249"), + ParseHex("#bb603c"), + ParseHex("#c76b4a"), + ParseHex("#a75536"), + ParseHex("#b63e36"), + ParseHex("#b5493a"), + ParseHex("#cd6d57"), + ParseHex("#711518"), + ParseHex("#e9c49d"), + ParseHex("#eedac3"), + ParseHex("#eecfbf"), + ParseHex("#ce536b"), + ParseHex("#b74a70"), + ParseHex("#b7757c"), + ParseHex("#612741"), + ParseHex("#7a4848"), + ParseHex("#3f3033"), + ParseHex("#8d746f"), + ParseHex("#4d3635"), + ParseHex("#6e3b31"), + ParseHex("#864735"), + ParseHex("#553d3a"), + ParseHex("#613936"), + ParseHex("#7a4b3a"), + ParseHex("#946943"), + ParseHex("#c39e6d"), + ParseHex("#513e32"), + ParseHex("#8b7859"), + ParseHex("#9b856b"), + ParseHex("#766051"), + ParseHex("#453b32") }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 5fad7a8e39..fcf091638e 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -95,21 +95,102 @@ namespace SixLabors.ImageSharp public static Color FromRgb(byte r, byte g, byte b) => new Color(r, g, b); /// - /// Creates a new instance from the string representing a color in hexadecimal form. + /// Creates a new instance of the struct + /// from the given hexadecimal string. /// /// /// The hexadecimal representation of the combined color components arranged /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. /// - /// Returns a that represents the color defined by the provided RGBA hex string. + /// + /// The . + /// [MethodImpl(InliningOptions.ShortMethod)] - public static Color FromHex(string hex) + public static Color ParseHex(string hex) { - var rgba = Rgba32.FromHex(hex); + var rgba = Rgba32.ParseHex(hex); return new Color(rgba); } + /// + /// Attempts to creates a new instance of the struct + /// from the given hexadecimal string. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool TryParseHex(string hex, out Color result) + { + result = default; + + if (Rgba32.TryParseHex(hex, out Rgba32 rgba)) + { + result = new Color(rgba); + return true; + } + + return false; + } + + /// + /// Creates a new instance of the struct + /// from the given input string. + /// + /// + /// The name of the color or the hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// + /// The . + /// + public static Color Parse(string input) + { + Guard.NotNull(input, nameof(input)); + + if (!TryParse(input, out Color color)) + { + throw new ArgumentException("Input string is not in the correct format.", nameof(input)); + } + + return color; + } + + /// + /// Attempts to creates a new instance of the struct + /// from the given input string. + /// + /// + /// The name of the color or the hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + public static bool TryParse(string input, out Color result) + { + result = default; + + if (string.IsNullOrWhiteSpace(input)) + { + return false; + } + + if (NamedColorsLookupLazy.Value.TryGetValue(input, out result)) + { + return true; + } + + return TryParseHex(input, out result); + } + /// /// Alters the alpha channel of the color, returning a new instance. /// @@ -117,7 +198,7 @@ namespace SixLabors.ImageSharp /// The color having it's alpha channel altered. public Color WithAlpha(float alpha) { - Vector4 v = (Vector4)this; + var v = (Vector4)this; v.W = alpha; return new Color(v); } diff --git a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs index cff6e3b601..983a1eb8b3 100644 --- a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Common +namespace SixLabors.ImageSharp { /// /// Encapsulates a series of time saving extension methods to the interface. @@ -34,15 +34,11 @@ namespace SixLabors.ImageSharp.Common /// /// Generates a sequence of integral numbers within a specified range. /// - /// - /// The start index, inclusive. - /// + /// The start index, inclusive. /// /// A method that has one parameter and returns a calculating the end index. /// - /// - /// The incremental step. - /// + /// The incremental step. /// /// The that contains a range of sequential integral numbers. /// @@ -56,4 +52,4 @@ namespace SixLabors.ImageSharp.Common } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index bc45a45fdc..0d76810848 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; @@ -261,19 +259,12 @@ namespace SixLabors.ImageSharp } var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); + var operation = new RowIntervalOperation(this, target, configuration); - ParallelHelper.IterateRows( - this.Bounds(), + ParallelRowIterator.IterateRows( 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); - } - }); + this.Bounds(), + in operation); return target; } @@ -295,5 +286,39 @@ namespace SixLabors.ImageSharp group.Fill(value); } } + + /// + /// A implementing the clone logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + where TPixel2 : struct, IPixel + { + private readonly ImageFrame source; + private readonly ImageFrame target; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + 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/PixelFormats/ColorConstants.cs b/src/ImageSharp/PixelFormats/ColorConstants.cs deleted file mode 100644 index 14df385697..0000000000 --- a/src/ImageSharp/PixelFormats/ColorConstants.cs +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Provides useful color definitions. - /// - public static class ColorConstants - { - /// - /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. - /// - public static readonly Rgba32[] WebSafeColors = - { - Rgba32.AliceBlue, - Rgba32.AntiqueWhite, - Rgba32.Aqua, - Rgba32.Aquamarine, - Rgba32.Azure, - Rgba32.Beige, - Rgba32.Bisque, - Rgba32.Black, - Rgba32.BlanchedAlmond, - Rgba32.Blue, - Rgba32.BlueViolet, - Rgba32.Brown, - Rgba32.BurlyWood, - Rgba32.CadetBlue, - Rgba32.Chartreuse, - Rgba32.Chocolate, - Rgba32.Coral, - Rgba32.CornflowerBlue, - Rgba32.Cornsilk, - Rgba32.Crimson, - Rgba32.Cyan, - Rgba32.DarkBlue, - Rgba32.DarkCyan, - Rgba32.DarkGoldenrod, - Rgba32.DarkGray, - Rgba32.DarkGreen, - Rgba32.DarkKhaki, - Rgba32.DarkMagenta, - Rgba32.DarkOliveGreen, - Rgba32.DarkOrange, - Rgba32.DarkOrchid, - Rgba32.DarkRed, - Rgba32.DarkSalmon, - Rgba32.DarkSeaGreen, - Rgba32.DarkSlateBlue, - Rgba32.DarkSlateGray, - Rgba32.DarkTurquoise, - Rgba32.DarkViolet, - Rgba32.DeepPink, - Rgba32.DeepSkyBlue, - Rgba32.DimGray, - Rgba32.DodgerBlue, - Rgba32.Firebrick, - Rgba32.FloralWhite, - Rgba32.ForestGreen, - Rgba32.Fuchsia, - Rgba32.Gainsboro, - Rgba32.GhostWhite, - Rgba32.Gold, - Rgba32.Goldenrod, - Rgba32.Gray, - Rgba32.Green, - Rgba32.GreenYellow, - Rgba32.Honeydew, - Rgba32.HotPink, - Rgba32.IndianRed, - Rgba32.Indigo, - Rgba32.Ivory, - Rgba32.Khaki, - Rgba32.Lavender, - Rgba32.LavenderBlush, - Rgba32.LawnGreen, - Rgba32.LemonChiffon, - Rgba32.LightBlue, - Rgba32.LightCoral, - Rgba32.LightCyan, - Rgba32.LightGoldenrodYellow, - Rgba32.LightGray, - Rgba32.LightGreen, - Rgba32.LightPink, - Rgba32.LightSalmon, - Rgba32.LightSeaGreen, - Rgba32.LightSkyBlue, - Rgba32.LightSlateGray, - Rgba32.LightSteelBlue, - Rgba32.LightYellow, - Rgba32.Lime, - Rgba32.LimeGreen, - Rgba32.Linen, - Rgba32.Magenta, - Rgba32.Maroon, - Rgba32.MediumAquamarine, - Rgba32.MediumBlue, - Rgba32.MediumOrchid, - Rgba32.MediumPurple, - Rgba32.MediumSeaGreen, - Rgba32.MediumSlateBlue, - Rgba32.MediumSpringGreen, - Rgba32.MediumTurquoise, - Rgba32.MediumVioletRed, - Rgba32.MidnightBlue, - Rgba32.MintCream, - Rgba32.MistyRose, - Rgba32.Moccasin, - Rgba32.NavajoWhite, - Rgba32.Navy, - Rgba32.OldLace, - Rgba32.Olive, - Rgba32.OliveDrab, - Rgba32.Orange, - Rgba32.OrangeRed, - Rgba32.Orchid, - Rgba32.PaleGoldenrod, - Rgba32.PaleGreen, - Rgba32.PaleTurquoise, - Rgba32.PaleVioletRed, - Rgba32.PapayaWhip, - Rgba32.PeachPuff, - Rgba32.Peru, - Rgba32.Pink, - Rgba32.Plum, - Rgba32.PowderBlue, - Rgba32.Purple, - Rgba32.RebeccaPurple, - Rgba32.Red, - Rgba32.RosyBrown, - Rgba32.RoyalBlue, - Rgba32.SaddleBrown, - Rgba32.Salmon, - Rgba32.SandyBrown, - Rgba32.SeaGreen, - Rgba32.SeaShell, - Rgba32.Sienna, - Rgba32.Silver, - Rgba32.SkyBlue, - Rgba32.SlateBlue, - Rgba32.SlateGray, - Rgba32.Snow, - Rgba32.SpringGreen, - Rgba32.SteelBlue, - Rgba32.Tan, - Rgba32.Teal, - Rgba32.Thistle, - Rgba32.Tomato, - Rgba32.Transparent, - Rgba32.Turquoise, - Rgba32.Violet, - Rgba32.Wheat, - Rgba32.White, - Rgba32.WhiteSmoke, - Rgba32.Yellow, - Rgba32.YellowGreen - }; - - /// - /// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. - /// The hex codes were collected and defined by Nicholas Rougeux - /// - public static readonly Rgba32[] WernerColors = - { - Rgba32.FromHex("#f1e9cd"), - Rgba32.FromHex("#f2e7cf"), - Rgba32.FromHex("#ece6d0"), - Rgba32.FromHex("#f2eacc"), - Rgba32.FromHex("#f3e9ca"), - Rgba32.FromHex("#f2ebcd"), - Rgba32.FromHex("#e6e1c9"), - Rgba32.FromHex("#e2ddc6"), - Rgba32.FromHex("#cbc8b7"), - Rgba32.FromHex("#bfbbb0"), - Rgba32.FromHex("#bebeb3"), - Rgba32.FromHex("#b7b5ac"), - Rgba32.FromHex("#bab191"), - Rgba32.FromHex("#9c9d9a"), - Rgba32.FromHex("#8a8d84"), - Rgba32.FromHex("#5b5c61"), - Rgba32.FromHex("#555152"), - Rgba32.FromHex("#413f44"), - Rgba32.FromHex("#454445"), - Rgba32.FromHex("#423937"), - Rgba32.FromHex("#433635"), - Rgba32.FromHex("#252024"), - Rgba32.FromHex("#241f20"), - Rgba32.FromHex("#281f3f"), - Rgba32.FromHex("#1c1949"), - Rgba32.FromHex("#4f638d"), - Rgba32.FromHex("#383867"), - Rgba32.FromHex("#5c6b8f"), - Rgba32.FromHex("#657abb"), - Rgba32.FromHex("#6f88af"), - Rgba32.FromHex("#7994b5"), - Rgba32.FromHex("#6fb5a8"), - Rgba32.FromHex("#719ba2"), - Rgba32.FromHex("#8aa1a6"), - Rgba32.FromHex("#d0d5d3"), - Rgba32.FromHex("#8590ae"), - Rgba32.FromHex("#3a2f52"), - Rgba32.FromHex("#39334a"), - Rgba32.FromHex("#6c6d94"), - Rgba32.FromHex("#584c77"), - Rgba32.FromHex("#533552"), - Rgba32.FromHex("#463759"), - Rgba32.FromHex("#bfbac0"), - Rgba32.FromHex("#77747f"), - Rgba32.FromHex("#4a475c"), - Rgba32.FromHex("#b8bfaf"), - Rgba32.FromHex("#b2b599"), - Rgba32.FromHex("#979c84"), - Rgba32.FromHex("#5d6161"), - Rgba32.FromHex("#61ac86"), - Rgba32.FromHex("#a4b6a7"), - Rgba32.FromHex("#adba98"), - Rgba32.FromHex("#93b778"), - Rgba32.FromHex("#7d8c55"), - Rgba32.FromHex("#33431e"), - Rgba32.FromHex("#7c8635"), - Rgba32.FromHex("#8e9849"), - Rgba32.FromHex("#c2c190"), - Rgba32.FromHex("#67765b"), - Rgba32.FromHex("#ab924b"), - Rgba32.FromHex("#c8c76f"), - Rgba32.FromHex("#ccc050"), - Rgba32.FromHex("#ebdd99"), - Rgba32.FromHex("#ab9649"), - Rgba32.FromHex("#dbc364"), - Rgba32.FromHex("#e6d058"), - Rgba32.FromHex("#ead665"), - Rgba32.FromHex("#d09b2c"), - Rgba32.FromHex("#a36629"), - Rgba32.FromHex("#a77d35"), - Rgba32.FromHex("#f0d696"), - Rgba32.FromHex("#d7c485"), - Rgba32.FromHex("#f1d28c"), - Rgba32.FromHex("#efcc83"), - Rgba32.FromHex("#f3daa7"), - Rgba32.FromHex("#dfa837"), - Rgba32.FromHex("#ebbc71"), - Rgba32.FromHex("#d17c3f"), - Rgba32.FromHex("#92462f"), - Rgba32.FromHex("#be7249"), - Rgba32.FromHex("#bb603c"), - Rgba32.FromHex("#c76b4a"), - Rgba32.FromHex("#a75536"), - Rgba32.FromHex("#b63e36"), - Rgba32.FromHex("#b5493a"), - Rgba32.FromHex("#cd6d57"), - Rgba32.FromHex("#711518"), - Rgba32.FromHex("#e9c49d"), - Rgba32.FromHex("#eedac3"), - Rgba32.FromHex("#eecfbf"), - Rgba32.FromHex("#ce536b"), - Rgba32.FromHex("#b74a70"), - Rgba32.FromHex("#b7757c"), - Rgba32.FromHex("#612741"), - Rgba32.FromHex("#7a4848"), - Rgba32.FromHex("#3f3033"), - Rgba32.FromHex("#8d746f"), - Rgba32.FromHex("#4d3635"), - Rgba32.FromHex("#6e3b31"), - Rgba32.FromHex("#864735"), - Rgba32.FromHex("#553d3a"), - Rgba32.FromHex("#613936"), - Rgba32.FromHex("#7a4b3a"), - Rgba32.FromHex("#946943"), - Rgba32.FromHex("#c39e6d"), - Rgba32.FromHex("#513e32"), - Rgba32.FromHex("#8b7859"), - Rgba32.FromHex("#9b856b"), - Rgba32.FromHex("#766051"), - Rgba32.FromHex("#453b32") - }; - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs deleted file mode 100644 index f9cc3256cd..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs +++ /dev/null @@ -1,721 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Provides standardized definitions for named colors. - /// - public partial struct Rgba32 - { - /// - /// Represents a matching the W3C definition that has an hex value of #F0F8FF. - /// - public static readonly Rgba32 AliceBlue = Color.AliceBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #FAEBD7. - /// - public static readonly Rgba32 AntiqueWhite = Color.AntiqueWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FFFF. - /// - public static readonly Rgba32 Aqua = Color.Aqua; - - /// - /// Represents a matching the W3C definition that has an hex value of #7FFFD4. - /// - public static readonly Rgba32 Aquamarine = Color.Aquamarine; - - /// - /// Represents a matching the W3C definition that has an hex value of #F0FFFF. - /// - public static readonly Rgba32 Azure = Color.Azure; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5F5DC. - /// - public static readonly Rgba32 Beige = Color.Beige; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4C4. - /// - public static readonly Rgba32 Bisque = Color.Bisque; - - /// - /// Represents a matching the W3C definition that has an hex value of #000000. - /// - public static readonly Rgba32 Black = Color.Black; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFEBCD. - /// - public static readonly Rgba32 BlanchedAlmond = Color.BlanchedAlmond; - - /// - /// Represents a matching the W3C definition that has an hex value of #0000FF. - /// - public static readonly Rgba32 Blue = Color.Blue; - - /// - /// Represents a matching the W3C definition that has an hex value of #8A2BE2. - /// - public static readonly Rgba32 BlueViolet = Color.BlueViolet; - - /// - /// Represents a matching the W3C definition that has an hex value of #A52A2A. - /// - public static readonly Rgba32 Brown = Color.Brown; - - /// - /// Represents a matching the W3C definition that has an hex value of #DEB887. - /// - public static readonly Rgba32 BurlyWood = Color.BurlyWood; - - /// - /// Represents a matching the W3C definition that has an hex value of #5F9EA0. - /// - public static readonly Rgba32 CadetBlue = Color.CadetBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #7FFF00. - /// - public static readonly Rgba32 Chartreuse = Color.Chartreuse; - - /// - /// Represents a matching the W3C definition that has an hex value of #D2691E. - /// - public static readonly Rgba32 Chocolate = Color.Chocolate; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF7F50. - /// - public static readonly Rgba32 Coral = Color.Coral; - - /// - /// Represents a matching the W3C definition that has an hex value of #6495ED. - /// - public static readonly Rgba32 CornflowerBlue = Color.CornflowerBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF8DC. - /// - public static readonly Rgba32 Cornsilk = Color.Cornsilk; - - /// - /// Represents a matching the W3C definition that has an hex value of #DC143C. - /// - public static readonly Rgba32 Crimson = Color.Crimson; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FFFF. - /// - public static readonly Rgba32 Cyan = Color.Cyan; - - /// - /// Represents a matching the W3C definition that has an hex value of #00008B. - /// - public static readonly Rgba32 DarkBlue = Color.DarkBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #008B8B. - /// - public static readonly Rgba32 DarkCyan = Color.DarkCyan; - - /// - /// Represents a matching the W3C definition that has an hex value of #B8860B. - /// - public static readonly Rgba32 DarkGoldenrod = Color.DarkGoldenrod; - - /// - /// Represents a matching the W3C definition that has an hex value of #A9A9A9. - /// - public static readonly Rgba32 DarkGray = Color.DarkGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #006400. - /// - public static readonly Rgba32 DarkGreen = Color.DarkGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #BDB76B. - /// - public static readonly Rgba32 DarkKhaki = Color.DarkKhaki; - - /// - /// Represents a matching the W3C definition that has an hex value of #8B008B. - /// - public static readonly Rgba32 DarkMagenta = Color.DarkMagenta; - - /// - /// Represents a matching the W3C definition that has an hex value of #556B2F. - /// - public static readonly Rgba32 DarkOliveGreen = Color.DarkOliveGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF8C00. - /// - public static readonly Rgba32 DarkOrange = Color.DarkOrange; - - /// - /// Represents a matching the W3C definition that has an hex value of #9932CC. - /// - public static readonly Rgba32 DarkOrchid = Color.DarkOrchid; - - /// - /// Represents a matching the W3C definition that has an hex value of #8B0000. - /// - public static readonly Rgba32 DarkRed = Color.DarkRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #E9967A. - /// - public static readonly Rgba32 DarkSalmon = Color.DarkSalmon; - - /// - /// Represents a matching the W3C definition that has an hex value of #8FBC8B. - /// - public static readonly Rgba32 DarkSeaGreen = Color.DarkSeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #483D8B. - /// - public static readonly Rgba32 DarkSlateBlue = Color.DarkSlateBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #2F4F4F. - /// - public static readonly Rgba32 DarkSlateGray = Color.DarkSlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #00CED1. - /// - public static readonly Rgba32 DarkTurquoise = Color.DarkTurquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #9400D3. - /// - public static readonly Rgba32 DarkViolet = Color.DarkViolet; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF1493. - /// - public static readonly Rgba32 DeepPink = Color.DeepPink; - - /// - /// Represents a matching the W3C definition that has an hex value of #00BFFF. - /// - public static readonly Rgba32 DeepSkyBlue = Color.DeepSkyBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #696969. - /// - public static readonly Rgba32 DimGray = Color.DimGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #1E90FF. - /// - public static readonly Rgba32 DodgerBlue = Color.DodgerBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #B22222. - /// - public static readonly Rgba32 Firebrick = Color.Firebrick; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFAF0. - /// - public static readonly Rgba32 FloralWhite = Color.FloralWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #228B22. - /// - public static readonly Rgba32 ForestGreen = Color.ForestGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF00FF. - /// - public static readonly Rgba32 Fuchsia = Color.Fuchsia; - - /// - /// Represents a matching the W3C definition that has an hex value of #DCDCDC. - /// - public static readonly Rgba32 Gainsboro = Color.Gainsboro; - - /// - /// Represents a matching the W3C definition that has an hex value of #F8F8FF. - /// - public static readonly Rgba32 GhostWhite = Color.GhostWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFD700. - /// - public static readonly Rgba32 Gold = Color.Gold; - - /// - /// Represents a matching the W3C definition that has an hex value of #DAA520. - /// - public static readonly Rgba32 Goldenrod = Color.Goldenrod; - - /// - /// Represents a matching the W3C definition that has an hex value of #808080. - /// - public static readonly Rgba32 Gray = Color.Gray; - - /// - /// Represents a matching the W3C definition that has an hex value of #008000. - /// - public static readonly Rgba32 Green = Color.Green; - - /// - /// Represents a matching the W3C definition that has an hex value of #ADFF2F. - /// - public static readonly Rgba32 GreenYellow = Color.GreenYellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #F0FFF0. - /// - public static readonly Rgba32 Honeydew = Color.Honeydew; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF69B4. - /// - public static readonly Rgba32 HotPink = Color.HotPink; - - /// - /// Represents a matching the W3C definition that has an hex value of #CD5C5C. - /// - public static readonly Rgba32 IndianRed = Color.IndianRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #4B0082. - /// - public static readonly Rgba32 Indigo = Color.Indigo; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFF0. - /// - public static readonly Rgba32 Ivory = Color.Ivory; - - /// - /// Represents a matching the W3C definition that has an hex value of #F0E68C. - /// - public static readonly Rgba32 Khaki = Color.Khaki; - - /// - /// Represents a matching the W3C definition that has an hex value of #E6E6FA. - /// - public static readonly Rgba32 Lavender = Color.Lavender; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF0F5. - /// - public static readonly Rgba32 LavenderBlush = Color.LavenderBlush; - - /// - /// Represents a matching the W3C definition that has an hex value of #7CFC00. - /// - public static readonly Rgba32 LawnGreen = Color.LawnGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFACD. - /// - public static readonly Rgba32 LemonChiffon = Color.LemonChiffon; - - /// - /// Represents a matching the W3C definition that has an hex value of #ADD8E6. - /// - public static readonly Rgba32 LightBlue = Color.LightBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #F08080. - /// - public static readonly Rgba32 LightCoral = Color.LightCoral; - - /// - /// Represents a matching the W3C definition that has an hex value of #E0FFFF. - /// - public static readonly Rgba32 LightCyan = Color.LightCyan; - - /// - /// Represents a matching the W3C definition that has an hex value of #FAFAD2. - /// - public static readonly Rgba32 LightGoldenrodYellow = Color.LightGoldenrodYellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #D3D3D3. - /// - public static readonly Rgba32 LightGray = Color.LightGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #90EE90. - /// - public static readonly Rgba32 LightGreen = Color.LightGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFB6C1. - /// - public static readonly Rgba32 LightPink = Color.LightPink; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFA07A. - /// - public static readonly Rgba32 LightSalmon = Color.LightSalmon; - - /// - /// Represents a matching the W3C definition that has an hex value of #20B2AA. - /// - public static readonly Rgba32 LightSeaGreen = Color.LightSeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #87CEFA. - /// - public static readonly Rgba32 LightSkyBlue = Color.LightSkyBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #778899. - /// - public static readonly Rgba32 LightSlateGray = Color.LightSlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #B0C4DE. - /// - public static readonly Rgba32 LightSteelBlue = Color.LightSteelBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFE0. - /// - public static readonly Rgba32 LightYellow = Color.LightYellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FF00. - /// - public static readonly Rgba32 Lime = Color.Lime; - - /// - /// Represents a matching the W3C definition that has an hex value of #32CD32. - /// - public static readonly Rgba32 LimeGreen = Color.LimeGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FAF0E6. - /// - public static readonly Rgba32 Linen = Color.Linen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF00FF. - /// - public static readonly Rgba32 Magenta = Color.Magenta; - - /// - /// Represents a matching the W3C definition that has an hex value of #800000. - /// - public static readonly Rgba32 Maroon = Color.Maroon; - - /// - /// Represents a matching the W3C definition that has an hex value of #66CDAA. - /// - public static readonly Rgba32 MediumAquamarine = Color.MediumAquamarine; - - /// - /// Represents a matching the W3C definition that has an hex value of #0000CD. - /// - public static readonly Rgba32 MediumBlue = Color.MediumBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #BA55D3. - /// - public static readonly Rgba32 MediumOrchid = Color.MediumOrchid; - - /// - /// Represents a matching the W3C definition that has an hex value of #9370DB. - /// - public static readonly Rgba32 MediumPurple = Color.MediumPurple; - - /// - /// Represents a matching the W3C definition that has an hex value of #3CB371. - /// - public static readonly Rgba32 MediumSeaGreen = Color.MediumSeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #7B68EE. - /// - public static readonly Rgba32 MediumSlateBlue = Color.MediumSlateBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FA9A. - /// - public static readonly Rgba32 MediumSpringGreen = Color.MediumSpringGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #48D1CC. - /// - public static readonly Rgba32 MediumTurquoise = Color.MediumTurquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #C71585. - /// - public static readonly Rgba32 MediumVioletRed = Color.MediumVioletRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #191970. - /// - public static readonly Rgba32 MidnightBlue = Color.MidnightBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5FFFA. - /// - public static readonly Rgba32 MintCream = Color.MintCream; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4E1. - /// - public static readonly Rgba32 MistyRose = Color.MistyRose; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4B5. - /// - public static readonly Rgba32 Moccasin = Color.Moccasin; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFDEAD. - /// - public static readonly Rgba32 NavajoWhite = Color.NavajoWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #000080. - /// - public static readonly Rgba32 Navy = Color.Navy; - - /// - /// Represents a matching the W3C definition that has an hex value of #FDF5E6. - /// - public static readonly Rgba32 OldLace = Color.OldLace; - - /// - /// Represents a matching the W3C definition that has an hex value of #808000. - /// - public static readonly Rgba32 Olive = Color.Olive; - - /// - /// Represents a matching the W3C definition that has an hex value of #6B8E23. - /// - public static readonly Rgba32 OliveDrab = Color.OliveDrab; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFA500. - /// - public static readonly Rgba32 Orange = Color.Orange; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF4500. - /// - public static readonly Rgba32 OrangeRed = Color.OrangeRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #DA70D6. - /// - public static readonly Rgba32 Orchid = Color.Orchid; - - /// - /// Represents a matching the W3C definition that has an hex value of #EEE8AA. - /// - public static readonly Rgba32 PaleGoldenrod = Color.PaleGoldenrod; - - /// - /// Represents a matching the W3C definition that has an hex value of #98FB98. - /// - public static readonly Rgba32 PaleGreen = Color.PaleGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #AFEEEE. - /// - public static readonly Rgba32 PaleTurquoise = Color.PaleTurquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #DB7093. - /// - public static readonly Rgba32 PaleVioletRed = Color.PaleVioletRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFEFD5. - /// - public static readonly Rgba32 PapayaWhip = Color.PapayaWhip; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFDAB9. - /// - public static readonly Rgba32 PeachPuff = Color.PeachPuff; - - /// - /// Represents a matching the W3C definition that has an hex value of #CD853F. - /// - public static readonly Rgba32 Peru = Color.Peru; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFC0CB. - /// - public static readonly Rgba32 Pink = Color.Pink; - - /// - /// Represents a matching the W3C definition that has an hex value of #DDA0DD. - /// - public static readonly Rgba32 Plum = Color.Plum; - - /// - /// Represents a matching the W3C definition that has an hex value of #B0E0E6. - /// - public static readonly Rgba32 PowderBlue = Color.PowderBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #800080. - /// - public static readonly Rgba32 Purple = Color.Purple; - - /// - /// Represents a matching the W3C definition that has an hex value of #663399. - /// - public static readonly Rgba32 RebeccaPurple = Color.RebeccaPurple; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF0000. - /// - public static readonly Rgba32 Red = Color.Red; - - /// - /// Represents a matching the W3C definition that has an hex value of #BC8F8F. - /// - public static readonly Rgba32 RosyBrown = Color.RosyBrown; - - /// - /// Represents a matching the W3C definition that has an hex value of #4169E1. - /// - public static readonly Rgba32 RoyalBlue = Color.RoyalBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #8B4513. - /// - public static readonly Rgba32 SaddleBrown = Color.SaddleBrown; - - /// - /// Represents a matching the W3C definition that has an hex value of #FA8072. - /// - public static readonly Rgba32 Salmon = Color.Salmon; - - /// - /// Represents a matching the W3C definition that has an hex value of #F4A460. - /// - public static readonly Rgba32 SandyBrown = Color.SandyBrown; - - /// - /// Represents a matching the W3C definition that has an hex value of #2E8B57. - /// - public static readonly Rgba32 SeaGreen = Color.SeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF5EE. - /// - public static readonly Rgba32 SeaShell = Color.SeaShell; - - /// - /// Represents a matching the W3C definition that has an hex value of #A0522D. - /// - public static readonly Rgba32 Sienna = Color.Sienna; - - /// - /// Represents a matching the W3C definition that has an hex value of #C0C0C0. - /// - public static readonly Rgba32 Silver = Color.Silver; - - /// - /// Represents a matching the W3C definition that has an hex value of #87CEEB. - /// - public static readonly Rgba32 SkyBlue = Color.SkyBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #6A5ACD. - /// - public static readonly Rgba32 SlateBlue = Color.SlateBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #708090. - /// - public static readonly Rgba32 SlateGray = Color.SlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFAFA. - /// - public static readonly Rgba32 Snow = Color.Snow; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FF7F. - /// - public static readonly Rgba32 SpringGreen = Color.SpringGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #4682B4. - /// - public static readonly Rgba32 SteelBlue = Color.SteelBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #D2B48C. - /// - public static readonly Rgba32 Tan = Color.Tan; - - /// - /// Represents a matching the W3C definition that has an hex value of #008080. - /// - public static readonly Rgba32 Teal = Color.Teal; - - /// - /// Represents a matching the W3C definition that has an hex value of #D8BFD8. - /// - public static readonly Rgba32 Thistle = Color.Thistle; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF6347. - /// - public static readonly Rgba32 Tomato = Color.Tomato; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFFF. - /// - public static readonly Rgba32 Transparent = Color.Transparent; - - /// - /// Represents a matching the W3C definition that has an hex value of #40E0D0. - /// - public static readonly Rgba32 Turquoise = Color.Turquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #EE82EE. - /// - public static readonly Rgba32 Violet = Color.Violet; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5DEB3. - /// - public static readonly Rgba32 Wheat = Color.Wheat; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFFF. - /// - public static readonly Rgba32 White = Color.White; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5F5F5. - /// - public static readonly Rgba32 WhiteSmoke = Color.WhiteSmoke; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFF00. - /// - public static readonly Rgba32 Yellow = Color.Yellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #9ACD32. - /// - public static readonly Rgba32 YellowGreen = Color.YellowGreen; - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 10631e2cf9..62f8d97e8f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -230,7 +230,8 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgba32 left, Rgba32 right) => !left.Equals(right); /// - /// Creates a new instance of the struct. + /// Creates a new instance of the struct + /// from the given hexadecimal string. /// /// /// The hexadecimal representation of the combined color components arranged @@ -239,19 +240,50 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . /// - public static Rgba32 FromHex(string hex) + [MethodImpl(InliningOptions.ShortMethod)] + public static Rgba32 ParseHex(string hex) + { + Guard.NotNull(hex, nameof(hex)); + + if (!TryParseHex(hex, out Rgba32 rgba)) + { + throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + } + + return rgba; + } + + /// + /// Attempts to creates a new instance of the struct + /// from the given hexadecimal string. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool TryParseHex(string hex, out Rgba32 result) { - Guard.NotNullOrWhiteSpace(hex, nameof(hex)); + result = default; + if (string.IsNullOrWhiteSpace(hex)) + { + return false; + } hex = ToRgbaHex(hex); if (hex is null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint packedValue)) { - throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + return false; } packedValue = BinaryPrimitives.ReverseEndianness(packedValue); - return Unsafe.As(ref packedValue); + result = Unsafe.As(ref packedValue); + return true; } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs index 67f09f3a5d..89c5f6ddba 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . /// - public static RgbaVector FromHex(string hex) => Color.FromHex(hex).ToPixel(); + public static RgbaVector FromHex(string hex) => Color.ParseHex(hex).ToPixel(); /// public PixelOperations CreatePixelOperations() => new PixelOperations(); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 0350c669ab..9c3a592d71 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; @@ -126,4 +126,4 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs index 95b01fd9d6..d391057a9b 100644 --- a/src/ImageSharp/Primitives/Rectangle.cs +++ b/src/ImageSharp/Primitives/Rectangle.cs @@ -460,4 +460,4 @@ namespace SixLabors.ImageSharp this.Width.Equals(other.Width) && this.Height.Equals(other.Height); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 380ce64d24..ed14a44e95 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -2,9 +2,9 @@ // 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.Binarization @@ -42,36 +42,66 @@ 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); - - ParallelHelper.IterateRows( - workingRect, + var operation = new RowIntervalOperation(interest, source, upper, lower, threshold, isAlphaOnly); + ParallelRowIterator.IterateRows( configuration, - rows => - { - Rgba32 rgba = default; - for (int y = rows.Min; y < rows.Max; y++) - { - Span row = source.GetPixelRowSpan(y); + interest, + in operation); + } + + /// + /// A implementing the clone logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly ImageFrame source; + private readonly TPixel upper; + private readonly TPixel lower; + private readonly byte threshold; + private readonly int minX; + private readonly int maxX; + private readonly bool isAlphaOnly; - for (int x = startX; x < endX; x++) - { - ref TPixel color = ref row[x]; - color.ToRgba32(ref rgba); + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Rectangle bounds, + ImageFrame source, + TPixel upper, + TPixel lower, + byte threshold, + bool isAlphaOnly) + { + this.source = source; + this.upper = upper; + this.lower = lower; + this.threshold = threshold; + this.minX = bounds.X; + this.maxX = bounds.Right; + 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.minX; x < this.maxX; 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; + } + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index e44076b3b6..023a4cea0d 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; @@ -269,17 +268,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution protected override void OnFrameApply(ImageFrame source) { // Preliminary gamma highlight pass - this.ApplyGammaExposure(source.PixelBuffer, this.SourceRectangle, this.Configuration); + var gammaOperation = new ApplyGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); + ParallelRowIterator.IterateRows( + this.Configuration, + 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)) - { - // 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 + var operation = new ApplyInverseGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma); + ParallelRowIterator.IterateRows( + this.Configuration, + this.SourceRectangle, + in operation); } /// @@ -295,216 +303,223 @@ 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 + 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(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W); + ParallelRowIterator.IterateRows( + configuration, + sourceRectangle, + in horizontalOperation); } } /// - /// 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 ApplyVerticalConvolutionRowIntervalOperation : IRowIntervalOperation { - 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; - - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourcePixels; + private readonly Complex64[] kernel; + private readonly int maxY; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyVerticalConvolutionRowIntervalOperation( + Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourcePixels, + 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; + } + + /// + [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 targetRowSpan = targetValues.GetRowSpanUnchecked(y).Slice(startX); + Span targetRowSpan = this.targetValues.GetRowSpanUnchecked(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, this.maxY, this.bounds.X, this.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 ApplyHorizontalConvolutionRowIntervalOperation : IRowIntervalOperation { - 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; - - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourceValues; + private readonly Complex64[] kernel; + private readonly float z; + private readonly float w; + private readonly int maxY; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyHorizontalConvolutionRowIntervalOperation( + Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourceValues, + Complex64[] kernel, + float z, + 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; + this.z = z; + this.w = w; + } + + /// + [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 targetRowSpan = targetValues.GetRowSpanUnchecked(y).Slice(startX); + Span targetRowSpan = this.targetValues.GetRowSpanUnchecked(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, this.maxY, this.bounds.X, this.maxX, this.z, this.w); } - }); + } + } } /// - /// Applies the gamma correction/highlight to the input pixel buffer. + /// A implementing the gamma exposure 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 ApplyGammaExposureRowIntervalOperation : IRowIntervalOperation { - 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; - - ParallelHelper.IterateRowsWithTempBuffer( - workingRectangle, - configuration, - (rows, vectorBuffer) => + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Configuration configuration; + private readonly float gamma; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyGammaExposureRowIntervalOperation( + 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, Span span) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X); + 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++) { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpanUnchecked(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, span, targetRowSpan); + } + } } /// - /// Applies the inverse gamma correction/highlight pass, and converts the input buffer into pixel values. + /// A implementing the inverse gamma exposure 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 ApplyInverseGammaExposureRowIntervalOperation : IRowIntervalOperation { - 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 expGamma = 1 / this.gamma; - - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourceValues; + private readonly Configuration configuration; + private readonly float inverseGamma; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyInverseGammaExposureRowIntervalOperation( + 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; + } + + /// + [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); + + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetPixelSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.GetRowSpanUnchecked(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.GetRowSpanUnchecked(y).Slice(startX); - Span sourceRowSpan = sourceValues.GetRowSpanUnchecked(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); + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs index 095c91bac0..50004655fd 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 1e30d6a4a6..9d68196f51 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -3,9 +3,9 @@ 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; @@ -60,79 +60,105 @@ 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)) + var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha); + + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly int maxY; + private readonly int maxX; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernelY; + private readonly DenseMatrix kernelX; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernelY, + DenseMatrix kernelX, + Configuration configuration, + bool preserveAlpha) { - source.CopyTo(targetPixels); + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernelY = kernelY; + this.kernelX = kernelX; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Span span) + { + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); - ParallelHelper.IterateRowsWithTempBuffer( - workingRectangle, - this.Configuration, - (rows, vectorBuffer) => + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve2D3( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.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.GetRowSpanUnchecked(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 spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } + + 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 f36d5d6d06..809cda8a35 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -3,9 +3,9 @@ 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; @@ -59,95 +59,104 @@ 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 + var horizontalOperation = new RowIntervalOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in horizontalOperation); + + // Vertical convolution + var verticalOperation = new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in verticalOperation); } /// - /// 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 RowIntervalOperation : IRowIntervalOperation { - 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; - - ParallelHelper.IterateRowsWithTempBuffer( - 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; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + 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; + } - for (int y = rows.Min; y < rows.Max; y++) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Span span) + { + ref Vector4 spanRef = 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.GetRowSpanUnchecked(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) { - Span targetRowSpan = targetPixels.GetRowSpanUnchecked(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 spanRef, + 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 spanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + + 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 779360bde0..426a2800cf 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -3,9 +3,9 @@ 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; @@ -51,76 +51,99 @@ 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; + var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); - using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) - { - source.CopyTo(targetPixels); + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly int maxY; + private readonly int maxX; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; - ParallelHelper.IterateRowsWithTempBuffer( - workingRectangle, - this.Configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernel, + Configuration configuration, + 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; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX); - PixelOperations.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Span span) + { + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); - 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); - } - } + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan); - } - }); + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs index 31c4fad79f..c8c57fc293 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); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 29178300e2..2764b0b3f7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -1,12 +1,10 @@ // 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; - -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; @@ -55,88 +53,79 @@ 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; + 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); + // 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, interest)) { - 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; + // Additional runs + for (int i = 1; i < kernels.Length; i++) + { + using ImageFrame pass = cleanCopy.Clone(); - // Reset offset if necessary. - if (minX > 0) + using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, interest)) { - shiftX = 0; + processor.Apply(pass); } - if (minY > 0) - { - shiftY = 0; - } + var operation = new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, interest); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); + } + } - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly Buffer2D targetPixels; + private readonly Buffer2D passPixels; + private readonly int minX; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Buffer2D targetPixels, + Buffer2D passPixels, + Rectangle bounds) + { + this.targetPixels = targetPixels; + this.passPixels = passPixels; + this.minX = bounds.X; + this.maxX = bounds.Right; + } - // Additional runs. - // ReSharper disable once ForCanBeConvertedToForeach - for (int i = 1; i < kernels.Length; i++) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) { - using (ImageFrame pass = cleanCopy.Clone()) + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpanUnchecked(y)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpanUnchecked(y)); + + 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; - - ParallelHelper.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.GetRowSpanUnchecked(offsetY)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpanUnchecked(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); - } - } - }); + // Grab the max components of the two pixels + 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()); + + currentTargetPixel.FromVector4(pixelValue); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs index 3c1f82caa8..3d0a7a714d 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 f4f27a42de..506d34a3b8 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 a8b9093e5e..c1ce30cae1 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -2,9 +2,9 @@ // 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.Drawing @@ -99,18 +99,62 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing "Cannot draw image because the source image does not overlap the target image."); } - ParallelHelper.IterateRows( - workingRect, + var operation = new RowIntervalOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity); + ParallelRowIterator.IterateRows( configuration, - rows => + workingRect, + in operation); + } + + /// + /// A implementing the draw logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + 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 RowIntervalOperation( + 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); + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs new file mode 100644 index 0000000000..626ffd7168 --- /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/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 049fb6d02b..9bf18671a3 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; @@ -45,122 +43,145 @@ 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); - ParallelHelper.IterateRows( - workingRect, + var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels); + ParallelRowIterator.IterateRows( 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); + this.SourceRectangle, + in operation); - Span targetRowVector4Span = targetRowBuffer.Memory.Span; - Span targetRowAreaVector4Span = targetRowVector4Span.Slice(startX, rectangleWidth); - - 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 RowIntervalOperation : IRowIntervalOperation + { + 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 RowIntervalOperation( + 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.GetRowSpanUnchecked(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.GetRowSpanUnchecked(y).Slice(this.bounds.X, this.bounds.Width); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs index 5bdc0bc80b..9563f87187 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/PixelRowDelegateProcessorBase{TPixel}.cs deleted file mode 100644 index 019509dc23..0000000000 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.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.Numerics; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; - -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 - where TPixel : struct, IPixel - { - /// - /// The to apply during the pixel conversions. - /// - private readonly PixelConversionModifiers modifiers; - - /// - /// Initializes a new instance of the class. - /// - /// 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) - : base(configuration, source, sourceRectangle) - => this.modifiers = modifiers; - - /// - 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; - - ParallelHelper.IterateRowsWithTempBuffer( - 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); - } - }); - } - - /// - /// 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); - } -} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs new file mode 100644 index 0000000000..44ade727a7 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -0,0 +1,104 @@ +// 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.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Effects +{ + /// + /// The base class for all processors that accept a user defined row processing delegate. + /// + /// The pixel format. + /// 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. + /// + /// 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. + public PixelRowDelegateProcessor( + in TDelegate rowDelegate, + Configuration configuration, + PixelConversionModifiers modifiers, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.rowDelegate = rowDelegate; + this.modifiers = modifiers; + } + + /// + 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( + this.Configuration, + interest, + in operation); + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly int startX; + private readonly ImageFrame source; + private readonly Configuration configuration; + private readonly PixelConversionModifiers modifiers; + private readonly TDelegate rowProcessor; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + int startX, + ImageFrame source, + Configuration configuration, + PixelConversionModifiers modifiers, + in TDelegate rowProcessor) + { + this.startX = startX; + this.source = source; + this.configuration = configuration; + this.modifiers = modifiers; + this.rowProcessor = rowProcessor; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Span span) + { + for (int y = rows.Min; y < rows.Max; y++) + { + 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(span, new Point(this.startX, y)); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, 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 da917eaf39..0000000000 --- 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/PixelateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs index df85afc5eb..157ac7df17 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs @@ -3,10 +3,9 @@ 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 @@ -29,86 +28,76 @@ 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; /// 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)); - // Reset offset if necessary. - if (minX > 0) + // 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 RowOperation(interest, size, source).Invoke); + } + + private readonly struct RowOperation + { + 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 RowOperation( + 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) + for (int oX = x; oX < x + this.size && oX < this.maxX; oX++) { - offsetPy--; + this.source[oX, oY] = pixel; } - - Span row = source.GetPixelRowSpan(offsetY + offsetPy); - - for (int x = minX; x < maxX; x += size) - { - 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; - } - } - } - }); + } + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs index bf21f5b9b1..362b158101 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 901a3a9856..0000000000 --- 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); - } -} diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index 28a5837dea..19142ceb06 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -3,8 +3,9 @@ using System; using System.Numerics; +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.Filters @@ -35,27 +36,51 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startX = interest.X; + var operation = new RowIntervalOperation(interest.X, source, this.definition.Matrix, this.Configuration); - ColorMatrix matrix = this.definition.Matrix; - - ParallelHelper.IterateRowsWithTempBuffer( - interest, + ParallelRowIterator.IterateRows( 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); - } - }); + interest, + in operation); + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly int startX; + private readonly ImageFrame source; + private readonly ColorMatrix matrix; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + 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, Span span) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span); + + Vector4Utils.Transform(span, ref Unsafe.AsRef(this.matrix)); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan); + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index 227d1b9696..286eab8c0b 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,11 @@ 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; - } - }); + var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source); + ParallelRowIterator.IterateRows( + this.Configuration, + new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), + in operation); ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); @@ -416,6 +370,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. /// @@ -431,7 +474,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; @@ -453,7 +495,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; @@ -470,57 +511,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.GetRowSpanUnchecked(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 operation = new RowIntervalOperation( + processor, + this.memoryAllocator, + this.cdfMinBuffer2D, + this.cdfLutBuffer2D, + this.tileYStartPositions, + this.tileWidth, + this.tileHeight, + this.luminanceLevels, + source); + + ParallelRowIterator.IterateRows( + this.configuration, + new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), + in operation); } [MethodImpl(InliningOptions.ShortMethod)] @@ -548,6 +553,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.GetRowSpanUnchecked(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.GetRowSpanUnchecked(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/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs index 987e4e392f..4d0786947a 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; } } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index ff34457fbb..07fa55c5d0 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; @@ -49,64 +47,125 @@ 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(interest, histogramBuffer, source, this.LuminanceLevels); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in grayscaleOperation); + + 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 + var cdfOperation = new CdfApplicationRowIntervalOperation(interest, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in cdfOperation); + } + + /// + /// A implementing the grayscale levels logic for . + /// + private readonly struct GrayscaleLevelsRowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly IMemoryOwner histogramBuffer; + private readonly ImageFrame source; + private readonly int luminanceLevels; + + [MethodImpl(InliningOptions.ShortMethod)] + public GrayscaleLevelsRowIntervalOperation( + Rectangle bounds, + IMemoryOwner histogramBuffer, + ImageFrame source, + int luminanceLevels) + { + this.bounds = bounds; + this.histogramBuffer = histogramBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; + } - using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) { - // Build the histogram of the grayscale levels. - ParallelHelper.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) + 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 CdfApplicationRowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly IMemoryOwner cdfBuffer; + private readonly ImageFrame source; + private readonly int luminanceLevels; + private readonly float numberOfPixelsMinusCdfMin; + + [MethodImpl(InliningOptions.ShortMethod)] + public CdfApplicationRowIntervalOperation( + 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; + } - // 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 - ParallelHelper.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 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)); + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index c4fabead2b..b796016d1a 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -3,9 +3,8 @@ using System; using System.Buffers; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -29,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) @@ -39,65 +36,70 @@ 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; - - // 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); - - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } - - if (minY > 0) - { - startY = 0; - } - - int width = maxX - minX; + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - 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(); + using IMemoryOwner colors = memoryAllocator.Allocate(interest.Width); + using IMemoryOwner amount = memoryAllocator.Allocate(interest.Width); - colorSpan.Fill(color); - amountSpan.Fill(graphicsOptions.BlendPercentage); + colors.GetSpan().Fill(color); + amount.GetSpan().Fill(graphicsOptions.BlendPercentage); - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - ParallelHelper.IterateRows( - workingRect, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destination = - source.GetPixelRowSpan(y - startY).Slice(minX - startX, width); + var operation = new RowIntervalOperation(configuration, interest, blender, amount, colors, source); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } - // 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()); - } - }); + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly PixelBlender blender; + private readonly IMemoryOwner amount; + private readonly IMemoryOwner colors; + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + 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; + } + + [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); + + // 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 363e670d02..21a4e13452 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -4,9 +4,8 @@ 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; @@ -39,78 +38,84 @@ 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); - if (minY > 0) + var operation = new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + 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 RowIntervalOperation( + 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, Span span) { - rowColors.GetSpan().Fill(glowColor); - - ParallelHelper.IterateRowsWithTempBuffer( - 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 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)); + 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); + + 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 3e037189da..3515a2891e 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -4,9 +4,8 @@ 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; @@ -20,7 +19,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays where TPixel : struct, IPixel { private readonly PixelBlender blender; - private readonly VignetteProcessor definition; /// @@ -40,80 +38,92 @@ 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) - { - startX = 0; - } + 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( + configuration, + interest, + in operation); + } - if (minY > 0) + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + 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 RowIntervalOperation( + 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, Span span) { - rowColors.GetSpan().Fill(vignetteColor); - - ParallelHelper.IterateRowsWithTempBuffer( - 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 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)); + 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/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index 5e732982c3..276919d605 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])]; } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 1b9ff82bf9..0d9055f340 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -3,8 +3,9 @@ using System; using System.Numerics; +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 @@ -16,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; /// @@ -49,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; @@ -58,70 +58,130 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { - ParallelHelper.IterateRows( - targetBounds, + var nnOperation = new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination); + ParallelRowIterator.IterateRows( 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]; - } - } - } - }); + targetBounds, + in nnOperation); return; } - var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); + using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - try + var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination); + ParallelRowIterator.IterateRows( + configuration, + targetBounds, + in operation); + } + + /// + /// A implementing the nearest neighbor resampler logic for . + /// + private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly Matrix3x2 matrix; + private readonly int maxX; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public NearestNeighborRowIntervalOperation( + Rectangle bounds, + ref Matrix3x2 matrix, + int maxX, + ImageFrame source, + ImageFrame destination) { - ParallelHelper.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]; + } + } + } } - finally + } + + /// + /// A implementing the transformation logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + 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 RowIntervalOperation( + 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; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Span span) { - kernel.Dispose(); + for (int y = rows.Min; y < rows.Max; y++) + { + 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++) + { + // 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/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index a286e8fa26..6ad7aa2a25 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{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.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -15,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. @@ -47,21 +48,51 @@ 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); - ParallelHelper.IterateRows( + var operation = new RowIntervalOperation(bounds, source, destination); + + ParallelRowIterator.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); - } - }); + in parallelSettings, + in operation); + } + + /// + /// A implementing the processor logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + 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 RowIntervalOperation(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); + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs index d3afc72057..877fa074e9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs @@ -3,9 +3,9 @@ using System; using System.Buffers; - +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 @@ -55,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); } } @@ -79,16 +76,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void FlipY(ImageFrame source, Configuration configuration) { - ParallelHelper.IterateRows( - source.Bounds(), + var operation = new RowIntervalOperation(source); + ParallelRowIterator.IterateRows( configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - source.GetPixelRowSpan(y).Reverse(); - } - }); + source.Bounds(), + in operation); + } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation(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(); + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 56df606a77..da071e3f22 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -3,9 +3,9 @@ using System; using System.Numerics; - +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 @@ -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. @@ -50,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; @@ -59,73 +58,126 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { - ParallelHelper.IterateRows( - targetBounds, + Rectangle sourceBounds = this.SourceRectangle; + + var nnOperation = new NearestNeighborRowIntervalOperation(sourceBounds, ref matrix, width, source, destination); + ParallelRowIterator.IterateRows( 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]; - } - } - } - }); + targetBounds, + in nnOperation); return; } - var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); + using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - try + var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination); + ParallelRowIterator.IterateRows( + configuration, + targetBounds, + in operation); + } + + private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly Matrix4x4 matrix; + private readonly int maxX; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public NearestNeighborRowIntervalOperation( + Rectangle bounds, + ref Matrix4x4 matrix, + int maxX, + ImageFrame source, + ImageFrame destination) { - ParallelHelper.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]; + } + } + } + } + } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + 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 RowIntervalOperation( + Configuration configuration, + TransformKernelMap kernelMap, + ref Matrix4x4 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, Span span) { - kernel.Dispose(); + for (int y = rows.Min; y < rows.Max; y++) + { + 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++) + { + // 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); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index b91a273732..5ee32f85ff 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; @@ -248,4 +248,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return new ResizeKernel(left, rowPtr, length); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 2e94f88aca..53810a5cc3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -2,9 +2,8 @@ // 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; @@ -24,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. @@ -77,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()); @@ -86,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,24 +95,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; - ParallelHelper.IterateRows( - targetWorkingRect, + var operation = new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination); + ParallelRowIterator.IterateRows( 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)]; - } - } - }); + interest, + in operation); return; } @@ -137,12 +118,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); } } @@ -166,5 +147,56 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.isDisposed = true; base.Dispose(disposing); } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + 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 RowIntervalOperation( + 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)]; + } + } + } + } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index ccd36780c9..d40a28b5d0 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; diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs index 8f1cf28ce4..086314a26a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -2,9 +2,9 @@ // 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.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -17,6 +17,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class RotateProcessor : AffineTransformProcessor where TPixel : struct, IPixel { + private readonly float degrees; + /// /// Initializes a new instance of the class. /// @@ -26,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) @@ -41,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; @@ -52,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 /// @@ -95,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) { @@ -133,25 +131,11 @@ 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; - - ParallelHelper.IterateRows( - source.Bounds(), + var operation = new Rotate180RowIntervalOperation(source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRows( 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]; - } - } - }); + source.Bounds(), + in operation); } /// @@ -162,31 +146,11 @@ 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(); - - ParallelHelper.IterateRows( - source.Bounds(), + var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRows( 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]; - } - } - } - }); + source.Bounds(), + in operation); } /// @@ -197,28 +161,132 @@ 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(); - - ParallelHelper.IterateRows( - source.Bounds(), + var operation = new Rotate90RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRows( configuration, - rows => + source.Bounds(), + in operation); + } + + private readonly struct Rotate180RowIntervalOperation : IRowIntervalOperation + { + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate180RowIntervalOperation( + 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 Rotate270RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate270RowIntervalOperation( + 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 Rotate90RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate90RowIntervalOperation( + 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]; } - }); + } + } + } } } } diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index bacaaa7093..3c3b3b5ece 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -26,6 +26,7 @@ + diff --git a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs index f0d7a54d08..93f5bc8d85 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.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.Drawing; @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var image = new Image(400, 400)) { - image[200, 200] = Rgba32.White; + image[200, 200] = Color.White; return image[200, 200]; } } diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index fc93fc04e2..fd0b213b39 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +#if Windows_NT +using System.Security.Principal; +using BenchmarkDotNet.Diagnostics.Windows; +#endif using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Environments; @@ -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 60b1fde8e0..3cf0a7da34 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -19,6 +19,7 @@ + diff --git a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs index 48f8dc1663..8a5cccd68b 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")] diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs index b40cfc6226..1676197d41 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public Size DoDiffuse() { - using (var image = new Image(Configuration.Default, 800, 800, Rgba32.BlanchedAlmond)) + using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) { image.Mutate(x => x.Diffuse()); diff --git a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs index c5cfcb6eb2..711669b14e 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public void Blur() { - using (var image = new Image(Configuration.Default, 400, 400, Rgba32.White)) + using (var image = new Image(Configuration.Default, 400, 400, Color.White)) { image.Mutate(c => c.GaussianBlur()); } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs index 294a487bc6..e16e376fe1 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public Size DoRotate() { - using (var image = new Image(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond)) + using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) { image.Mutate(x => x.Rotate(37.5F)); diff --git a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs index 125dc2a3b4..0ad27861b2 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public Size DoSkew() { - using (var image = new Image(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond)) + using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) { image.Mutate(x => x.Skew(20, 10)); diff --git a/tests/ImageSharp.Tests/Color/ColorTests.cs b/tests/ImageSharp.Tests/Color/ColorTests.cs index 2ac774f53e..c689431f33 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.cs @@ -3,9 +3,7 @@ using System; using System.Linq; - using SixLabors.ImageSharp.PixelFormats; - using Xunit; namespace SixLabors.ImageSharp.Tests @@ -15,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void WithAlpha() { - Color c1 = Color.FromRgba(111, 222, 55, 255); + var c1 = Color.FromRgba(111, 222, 55, 255); Color c2 = c1.WithAlpha(0.5f); var expected = new Rgba32(111, 222, 55, 128); @@ -56,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests public void ToHex() { string expected = "ABCD1234"; - Color color = Color.FromHex(expected); + var color = Color.ParseHex(expected); string actual = color.ToHex(); Assert.Equal(expected, actual); @@ -66,14 +64,22 @@ namespace SixLabors.ImageSharp.Tests public void WebSafePalette_IsCorrect() { Rgba32[] actualPalette = Color.WebSafePalette.ToArray().Select(c => (Rgba32)c).ToArray(); - Assert.Equal(ReferencePalette.WebSafeColors, actualPalette); + + for (int i = 0; i < ReferencePalette.WebSafeColors.Length; i++) + { + Assert.Equal((Rgba32)ReferencePalette.WebSafeColors[i], actualPalette[i]); + } } [Fact] public void WernerPalette_IsCorrect() { Rgba32[] actualPalette = Color.WernerPalette.ToArray().Select(c => (Rgba32)c).ToArray(); - Assert.Equal(ReferencePalette.WernerColors, actualPalette); + + for (int i = 0; i < ReferencePalette.WernerColors.Length; i++) + { + Assert.Equal((Rgba32)ReferencePalette.WernerColors[i], actualPalette[i]); + } } public class FromHex @@ -81,28 +87,134 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ShortHex() { - Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.FromHex("#fff")); - Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.FromHex("fff")); - Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32)Color.FromHex("000f")); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.ParseHex("#fff")); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.ParseHex("fff")); + Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32)Color.ParseHex("000f")); + } + + [Fact] + public void TryShortHex() + { + Assert.True(Color.TryParseHex("#fff", out Color actual)); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)actual); + + Assert.True(Color.TryParseHex("fff", out actual)); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)actual); + + Assert.True(Color.TryParseHex("000f", out actual)); + Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32)actual); } [Fact] public void LeadingPoundIsOptional() { - Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.FromHex("#008080")); - Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.FromHex("008080")); + Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.ParseHex("#008080")); + Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.ParseHex("008080")); } [Fact] public void ThrowsOnEmpty() { - Assert.Throws(() => Color.FromHex(string.Empty)); + Assert.Throws(() => Color.ParseHex(string.Empty)); + } + + [Fact] + public void ThrowsOnInvalid() + { + Assert.Throws(() => Color.ParseHex("!")); } [Fact] public void ThrowsOnNull() { - Assert.Throws(() => Color.FromHex(null)); + Assert.Throws(() => Color.ParseHex(null)); + } + + [Fact] + public void FalseOnEmpty() + { + Assert.False(Color.TryParseHex(string.Empty, out Color _)); + } + + [Fact] + public void FalseOnInvalid() + { + Assert.False(Color.TryParseHex("!", out Color _)); + } + + [Fact] + public void FalseOnNull() + { + Assert.False(Color.TryParseHex(null, out Color _)); + } + } + + public class FromString + { + [Fact] + public void ColorNames() + { + foreach (string name in ReferencePalette.ColorNames.Keys) + { + Rgba32 expected = ReferencePalette.ColorNames[name]; + Assert.Equal(expected, (Rgba32)Color.Parse(name)); + Assert.Equal(expected, (Rgba32)Color.Parse(name.ToLowerInvariant())); + Assert.Equal(expected, (Rgba32)Color.Parse(expected.ToHex())); + } + } + + [Fact] + public void TryColorNames() + { + foreach (string name in ReferencePalette.ColorNames.Keys) + { + Rgba32 expected = ReferencePalette.ColorNames[name]; + + Assert.True(Color.TryParse(name, out Color actual)); + Assert.Equal(expected, (Rgba32)actual); + + Assert.True(Color.TryParse(name.ToLowerInvariant(), out actual)); + Assert.Equal(expected, (Rgba32)actual); + + Assert.True(Color.TryParse(expected.ToHex(), out actual)); + Assert.Equal(expected, (Rgba32)actual); + } + } + + [Fact] + public void ThrowsOnEmpty() + { + Assert.Throws(() => Color.Parse(string.Empty)); + } + + [Fact] + public void ThrowsOnInvalid() + { + Assert.Throws(() => Color.Parse("!")); + } + + [Fact] + public void ThrowsOnNull() + { + Assert.Throws(() => Color.Parse(null)); + } + + [Fact] + public void FalseOnEmpty() + { + Assert.False(Color.TryParse(string.Empty, out Color _)); + } + + [Fact] + public void FalseOnInvalid() + { + Assert.False(Color.TryParse("!", out Color _)); + } + + [Fact] + public void FalseOnNull() + { + Assert.False(Color.TryParse(null, out Color _)); } } } diff --git a/tests/ImageSharp.Tests/Color/ReferencePalette.cs b/tests/ImageSharp.Tests/Color/ReferencePalette.cs index 9896731e69..d8403e27e2 100644 --- a/tests/ImageSharp.Tests/Color/ReferencePalette.cs +++ b/tests/ImageSharp.Tests/Color/ReferencePalette.cs @@ -1,7 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Collections.Generic; namespace SixLabors.ImageSharp.Tests { @@ -10,268 +11,422 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. /// - public static readonly Rgba32[] WebSafeColors = + public static readonly Color[] WebSafeColors = { - Rgba32.AliceBlue, - Rgba32.AntiqueWhite, - Rgba32.Aqua, - Rgba32.Aquamarine, - Rgba32.Azure, - Rgba32.Beige, - Rgba32.Bisque, - Rgba32.Black, - Rgba32.BlanchedAlmond, - Rgba32.Blue, - Rgba32.BlueViolet, - Rgba32.Brown, - Rgba32.BurlyWood, - Rgba32.CadetBlue, - Rgba32.Chartreuse, - Rgba32.Chocolate, - Rgba32.Coral, - Rgba32.CornflowerBlue, - Rgba32.Cornsilk, - Rgba32.Crimson, - Rgba32.Cyan, - Rgba32.DarkBlue, - Rgba32.DarkCyan, - Rgba32.DarkGoldenrod, - Rgba32.DarkGray, - Rgba32.DarkGreen, - Rgba32.DarkKhaki, - Rgba32.DarkMagenta, - Rgba32.DarkOliveGreen, - Rgba32.DarkOrange, - Rgba32.DarkOrchid, - Rgba32.DarkRed, - Rgba32.DarkSalmon, - Rgba32.DarkSeaGreen, - Rgba32.DarkSlateBlue, - Rgba32.DarkSlateGray, - Rgba32.DarkTurquoise, - Rgba32.DarkViolet, - Rgba32.DeepPink, - Rgba32.DeepSkyBlue, - Rgba32.DimGray, - Rgba32.DodgerBlue, - Rgba32.Firebrick, - Rgba32.FloralWhite, - Rgba32.ForestGreen, - Rgba32.Fuchsia, - Rgba32.Gainsboro, - Rgba32.GhostWhite, - Rgba32.Gold, - Rgba32.Goldenrod, - Rgba32.Gray, - Rgba32.Green, - Rgba32.GreenYellow, - Rgba32.Honeydew, - Rgba32.HotPink, - Rgba32.IndianRed, - Rgba32.Indigo, - Rgba32.Ivory, - Rgba32.Khaki, - Rgba32.Lavender, - Rgba32.LavenderBlush, - Rgba32.LawnGreen, - Rgba32.LemonChiffon, - Rgba32.LightBlue, - Rgba32.LightCoral, - Rgba32.LightCyan, - Rgba32.LightGoldenrodYellow, - Rgba32.LightGray, - Rgba32.LightGreen, - Rgba32.LightPink, - Rgba32.LightSalmon, - Rgba32.LightSeaGreen, - Rgba32.LightSkyBlue, - Rgba32.LightSlateGray, - Rgba32.LightSteelBlue, - Rgba32.LightYellow, - Rgba32.Lime, - Rgba32.LimeGreen, - Rgba32.Linen, - Rgba32.Magenta, - Rgba32.Maroon, - Rgba32.MediumAquamarine, - Rgba32.MediumBlue, - Rgba32.MediumOrchid, - Rgba32.MediumPurple, - Rgba32.MediumSeaGreen, - Rgba32.MediumSlateBlue, - Rgba32.MediumSpringGreen, - Rgba32.MediumTurquoise, - Rgba32.MediumVioletRed, - Rgba32.MidnightBlue, - Rgba32.MintCream, - Rgba32.MistyRose, - Rgba32.Moccasin, - Rgba32.NavajoWhite, - Rgba32.Navy, - Rgba32.OldLace, - Rgba32.Olive, - Rgba32.OliveDrab, - Rgba32.Orange, - Rgba32.OrangeRed, - Rgba32.Orchid, - Rgba32.PaleGoldenrod, - Rgba32.PaleGreen, - Rgba32.PaleTurquoise, - Rgba32.PaleVioletRed, - Rgba32.PapayaWhip, - Rgba32.PeachPuff, - Rgba32.Peru, - Rgba32.Pink, - Rgba32.Plum, - Rgba32.PowderBlue, - Rgba32.Purple, - Rgba32.RebeccaPurple, - Rgba32.Red, - Rgba32.RosyBrown, - Rgba32.RoyalBlue, - Rgba32.SaddleBrown, - Rgba32.Salmon, - Rgba32.SandyBrown, - Rgba32.SeaGreen, - Rgba32.SeaShell, - Rgba32.Sienna, - Rgba32.Silver, - Rgba32.SkyBlue, - Rgba32.SlateBlue, - Rgba32.SlateGray, - Rgba32.Snow, - Rgba32.SpringGreen, - Rgba32.SteelBlue, - Rgba32.Tan, - Rgba32.Teal, - Rgba32.Thistle, - Rgba32.Tomato, - Rgba32.Transparent, - Rgba32.Turquoise, - Rgba32.Violet, - Rgba32.Wheat, - Rgba32.White, - Rgba32.WhiteSmoke, - Rgba32.Yellow, - Rgba32.YellowGreen + Color.AliceBlue, + Color.AntiqueWhite, + Color.Aqua, + Color.Aquamarine, + Color.Azure, + Color.Beige, + Color.Bisque, + Color.Black, + Color.BlanchedAlmond, + Color.Blue, + Color.BlueViolet, + Color.Brown, + Color.BurlyWood, + Color.CadetBlue, + Color.Chartreuse, + Color.Chocolate, + Color.Coral, + Color.CornflowerBlue, + Color.Cornsilk, + Color.Crimson, + Color.Cyan, + Color.DarkBlue, + Color.DarkCyan, + Color.DarkGoldenrod, + Color.DarkGray, + Color.DarkGreen, + Color.DarkKhaki, + Color.DarkMagenta, + Color.DarkOliveGreen, + Color.DarkOrange, + Color.DarkOrchid, + Color.DarkRed, + Color.DarkSalmon, + Color.DarkSeaGreen, + Color.DarkSlateBlue, + Color.DarkSlateGray, + Color.DarkTurquoise, + Color.DarkViolet, + Color.DeepPink, + Color.DeepSkyBlue, + Color.DimGray, + Color.DodgerBlue, + Color.Firebrick, + Color.FloralWhite, + Color.ForestGreen, + Color.Fuchsia, + Color.Gainsboro, + Color.GhostWhite, + Color.Gold, + Color.Goldenrod, + Color.Gray, + Color.Green, + Color.GreenYellow, + Color.Honeydew, + Color.HotPink, + Color.IndianRed, + Color.Indigo, + Color.Ivory, + Color.Khaki, + Color.Lavender, + Color.LavenderBlush, + Color.LawnGreen, + Color.LemonChiffon, + Color.LightBlue, + Color.LightCoral, + Color.LightCyan, + Color.LightGoldenrodYellow, + Color.LightGray, + Color.LightGreen, + Color.LightPink, + Color.LightSalmon, + Color.LightSeaGreen, + Color.LightSkyBlue, + Color.LightSlateGray, + Color.LightSteelBlue, + Color.LightYellow, + Color.Lime, + Color.LimeGreen, + Color.Linen, + Color.Magenta, + Color.Maroon, + Color.MediumAquamarine, + Color.MediumBlue, + Color.MediumOrchid, + Color.MediumPurple, + Color.MediumSeaGreen, + Color.MediumSlateBlue, + Color.MediumSpringGreen, + Color.MediumTurquoise, + Color.MediumVioletRed, + Color.MidnightBlue, + Color.MintCream, + Color.MistyRose, + Color.Moccasin, + Color.NavajoWhite, + Color.Navy, + Color.OldLace, + Color.Olive, + Color.OliveDrab, + Color.Orange, + Color.OrangeRed, + Color.Orchid, + Color.PaleGoldenrod, + Color.PaleGreen, + Color.PaleTurquoise, + Color.PaleVioletRed, + Color.PapayaWhip, + Color.PeachPuff, + Color.Peru, + Color.Pink, + Color.Plum, + Color.PowderBlue, + Color.Purple, + Color.RebeccaPurple, + Color.Red, + Color.RosyBrown, + Color.RoyalBlue, + Color.SaddleBrown, + Color.Salmon, + Color.SandyBrown, + Color.SeaGreen, + Color.SeaShell, + Color.Sienna, + Color.Silver, + Color.SkyBlue, + Color.SlateBlue, + Color.SlateGray, + Color.Snow, + Color.SpringGreen, + Color.SteelBlue, + Color.Tan, + Color.Teal, + Color.Thistle, + Color.Tomato, + Color.Transparent, + Color.Turquoise, + Color.Violet, + Color.Wheat, + Color.White, + Color.WhiteSmoke, + Color.Yellow, + Color.YellowGreen }; /// /// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. /// The hex codes were collected and defined by Nicholas Rougeux /// - public static readonly Rgba32[] WernerColors = + public static readonly Color[] WernerColors = { - Rgba32.FromHex("#f1e9cd"), - Rgba32.FromHex("#f2e7cf"), - Rgba32.FromHex("#ece6d0"), - Rgba32.FromHex("#f2eacc"), - Rgba32.FromHex("#f3e9ca"), - Rgba32.FromHex("#f2ebcd"), - Rgba32.FromHex("#e6e1c9"), - Rgba32.FromHex("#e2ddc6"), - Rgba32.FromHex("#cbc8b7"), - Rgba32.FromHex("#bfbbb0"), - Rgba32.FromHex("#bebeb3"), - Rgba32.FromHex("#b7b5ac"), - Rgba32.FromHex("#bab191"), - Rgba32.FromHex("#9c9d9a"), - Rgba32.FromHex("#8a8d84"), - Rgba32.FromHex("#5b5c61"), - Rgba32.FromHex("#555152"), - Rgba32.FromHex("#413f44"), - Rgba32.FromHex("#454445"), - Rgba32.FromHex("#423937"), - Rgba32.FromHex("#433635"), - Rgba32.FromHex("#252024"), - Rgba32.FromHex("#241f20"), - Rgba32.FromHex("#281f3f"), - Rgba32.FromHex("#1c1949"), - Rgba32.FromHex("#4f638d"), - Rgba32.FromHex("#383867"), - Rgba32.FromHex("#5c6b8f"), - Rgba32.FromHex("#657abb"), - Rgba32.FromHex("#6f88af"), - Rgba32.FromHex("#7994b5"), - Rgba32.FromHex("#6fb5a8"), - Rgba32.FromHex("#719ba2"), - Rgba32.FromHex("#8aa1a6"), - Rgba32.FromHex("#d0d5d3"), - Rgba32.FromHex("#8590ae"), - Rgba32.FromHex("#3a2f52"), - Rgba32.FromHex("#39334a"), - Rgba32.FromHex("#6c6d94"), - Rgba32.FromHex("#584c77"), - Rgba32.FromHex("#533552"), - Rgba32.FromHex("#463759"), - Rgba32.FromHex("#bfbac0"), - Rgba32.FromHex("#77747f"), - Rgba32.FromHex("#4a475c"), - Rgba32.FromHex("#b8bfaf"), - Rgba32.FromHex("#b2b599"), - Rgba32.FromHex("#979c84"), - Rgba32.FromHex("#5d6161"), - Rgba32.FromHex("#61ac86"), - Rgba32.FromHex("#a4b6a7"), - Rgba32.FromHex("#adba98"), - Rgba32.FromHex("#93b778"), - Rgba32.FromHex("#7d8c55"), - Rgba32.FromHex("#33431e"), - Rgba32.FromHex("#7c8635"), - Rgba32.FromHex("#8e9849"), - Rgba32.FromHex("#c2c190"), - Rgba32.FromHex("#67765b"), - Rgba32.FromHex("#ab924b"), - Rgba32.FromHex("#c8c76f"), - Rgba32.FromHex("#ccc050"), - Rgba32.FromHex("#ebdd99"), - Rgba32.FromHex("#ab9649"), - Rgba32.FromHex("#dbc364"), - Rgba32.FromHex("#e6d058"), - Rgba32.FromHex("#ead665"), - Rgba32.FromHex("#d09b2c"), - Rgba32.FromHex("#a36629"), - Rgba32.FromHex("#a77d35"), - Rgba32.FromHex("#f0d696"), - Rgba32.FromHex("#d7c485"), - Rgba32.FromHex("#f1d28c"), - Rgba32.FromHex("#efcc83"), - Rgba32.FromHex("#f3daa7"), - Rgba32.FromHex("#dfa837"), - Rgba32.FromHex("#ebbc71"), - Rgba32.FromHex("#d17c3f"), - Rgba32.FromHex("#92462f"), - Rgba32.FromHex("#be7249"), - Rgba32.FromHex("#bb603c"), - Rgba32.FromHex("#c76b4a"), - Rgba32.FromHex("#a75536"), - Rgba32.FromHex("#b63e36"), - Rgba32.FromHex("#b5493a"), - Rgba32.FromHex("#cd6d57"), - Rgba32.FromHex("#711518"), - Rgba32.FromHex("#e9c49d"), - Rgba32.FromHex("#eedac3"), - Rgba32.FromHex("#eecfbf"), - Rgba32.FromHex("#ce536b"), - Rgba32.FromHex("#b74a70"), - Rgba32.FromHex("#b7757c"), - Rgba32.FromHex("#612741"), - Rgba32.FromHex("#7a4848"), - Rgba32.FromHex("#3f3033"), - Rgba32.FromHex("#8d746f"), - Rgba32.FromHex("#4d3635"), - Rgba32.FromHex("#6e3b31"), - Rgba32.FromHex("#864735"), - Rgba32.FromHex("#553d3a"), - Rgba32.FromHex("#613936"), - Rgba32.FromHex("#7a4b3a"), - Rgba32.FromHex("#946943"), - Rgba32.FromHex("#c39e6d"), - Rgba32.FromHex("#513e32"), - Rgba32.FromHex("#8b7859"), - Rgba32.FromHex("#9b856b"), - Rgba32.FromHex("#766051"), - Rgba32.FromHex("#453b32") + Color.ParseHex("#f1e9cd"), + Color.ParseHex("#f2e7cf"), + Color.ParseHex("#ece6d0"), + Color.ParseHex("#f2eacc"), + Color.ParseHex("#f3e9ca"), + Color.ParseHex("#f2ebcd"), + Color.ParseHex("#e6e1c9"), + Color.ParseHex("#e2ddc6"), + Color.ParseHex("#cbc8b7"), + Color.ParseHex("#bfbbb0"), + Color.ParseHex("#bebeb3"), + Color.ParseHex("#b7b5ac"), + Color.ParseHex("#bab191"), + Color.ParseHex("#9c9d9a"), + Color.ParseHex("#8a8d84"), + Color.ParseHex("#5b5c61"), + Color.ParseHex("#555152"), + Color.ParseHex("#413f44"), + Color.ParseHex("#454445"), + Color.ParseHex("#423937"), + Color.ParseHex("#433635"), + Color.ParseHex("#252024"), + Color.ParseHex("#241f20"), + Color.ParseHex("#281f3f"), + Color.ParseHex("#1c1949"), + Color.ParseHex("#4f638d"), + Color.ParseHex("#383867"), + Color.ParseHex("#5c6b8f"), + Color.ParseHex("#657abb"), + Color.ParseHex("#6f88af"), + Color.ParseHex("#7994b5"), + Color.ParseHex("#6fb5a8"), + Color.ParseHex("#719ba2"), + Color.ParseHex("#8aa1a6"), + Color.ParseHex("#d0d5d3"), + Color.ParseHex("#8590ae"), + Color.ParseHex("#3a2f52"), + Color.ParseHex("#39334a"), + Color.ParseHex("#6c6d94"), + Color.ParseHex("#584c77"), + Color.ParseHex("#533552"), + Color.ParseHex("#463759"), + Color.ParseHex("#bfbac0"), + Color.ParseHex("#77747f"), + Color.ParseHex("#4a475c"), + Color.ParseHex("#b8bfaf"), + Color.ParseHex("#b2b599"), + Color.ParseHex("#979c84"), + Color.ParseHex("#5d6161"), + Color.ParseHex("#61ac86"), + Color.ParseHex("#a4b6a7"), + Color.ParseHex("#adba98"), + Color.ParseHex("#93b778"), + Color.ParseHex("#7d8c55"), + Color.ParseHex("#33431e"), + Color.ParseHex("#7c8635"), + Color.ParseHex("#8e9849"), + Color.ParseHex("#c2c190"), + Color.ParseHex("#67765b"), + Color.ParseHex("#ab924b"), + Color.ParseHex("#c8c76f"), + Color.ParseHex("#ccc050"), + Color.ParseHex("#ebdd99"), + Color.ParseHex("#ab9649"), + Color.ParseHex("#dbc364"), + Color.ParseHex("#e6d058"), + Color.ParseHex("#ead665"), + Color.ParseHex("#d09b2c"), + Color.ParseHex("#a36629"), + Color.ParseHex("#a77d35"), + Color.ParseHex("#f0d696"), + Color.ParseHex("#d7c485"), + Color.ParseHex("#f1d28c"), + Color.ParseHex("#efcc83"), + Color.ParseHex("#f3daa7"), + Color.ParseHex("#dfa837"), + Color.ParseHex("#ebbc71"), + Color.ParseHex("#d17c3f"), + Color.ParseHex("#92462f"), + Color.ParseHex("#be7249"), + Color.ParseHex("#bb603c"), + Color.ParseHex("#c76b4a"), + Color.ParseHex("#a75536"), + Color.ParseHex("#b63e36"), + Color.ParseHex("#b5493a"), + Color.ParseHex("#cd6d57"), + Color.ParseHex("#711518"), + Color.ParseHex("#e9c49d"), + Color.ParseHex("#eedac3"), + Color.ParseHex("#eecfbf"), + Color.ParseHex("#ce536b"), + Color.ParseHex("#b74a70"), + Color.ParseHex("#b7757c"), + Color.ParseHex("#612741"), + Color.ParseHex("#7a4848"), + Color.ParseHex("#3f3033"), + Color.ParseHex("#8d746f"), + Color.ParseHex("#4d3635"), + Color.ParseHex("#6e3b31"), + Color.ParseHex("#864735"), + Color.ParseHex("#553d3a"), + Color.ParseHex("#613936"), + Color.ParseHex("#7a4b3a"), + Color.ParseHex("#946943"), + Color.ParseHex("#c39e6d"), + Color.ParseHex("#513e32"), + Color.ParseHex("#8b7859"), + Color.ParseHex("#9b856b"), + Color.ParseHex("#766051"), + Color.ParseHex("#453b32") }; + + public static readonly Dictionary ColorNames = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { nameof(Color.AliceBlue), Color.AliceBlue }, + { nameof(Color.AntiqueWhite), Color.AntiqueWhite }, + { nameof(Color.Aqua), Color.Aqua }, + { nameof(Color.Aquamarine), Color.Aquamarine }, + { nameof(Color.Azure), Color.Azure }, + { nameof(Color.Beige), Color.Beige }, + { nameof(Color.Bisque), Color.Bisque }, + { nameof(Color.Black), Color.Black }, + { nameof(Color.BlanchedAlmond), Color.BlanchedAlmond }, + { nameof(Color.Blue), Color.Blue }, + { nameof(Color.BlueViolet), Color.BlueViolet }, + { nameof(Color.Brown), Color.Brown }, + { nameof(Color.BurlyWood), Color.BurlyWood }, + { nameof(Color.CadetBlue), Color.CadetBlue }, + { nameof(Color.Chartreuse), Color.Chartreuse }, + { nameof(Color.Chocolate), Color.Chocolate }, + { nameof(Color.Coral), Color.Coral }, + { nameof(Color.CornflowerBlue), Color.CornflowerBlue }, + { nameof(Color.Cornsilk), Color.Cornsilk }, + { nameof(Color.Crimson), Color.Crimson }, + { nameof(Color.Cyan), Color.Cyan }, + { nameof(Color.DarkBlue), Color.DarkBlue }, + { nameof(Color.DarkCyan), Color.DarkCyan }, + { nameof(Color.DarkGoldenrod), Color.DarkGoldenrod }, + { nameof(Color.DarkGray), Color.DarkGray }, + { nameof(Color.DarkGreen), Color.DarkGreen }, + { nameof(Color.DarkGrey), Color.DarkGrey }, + { nameof(Color.DarkKhaki), Color.DarkKhaki }, + { nameof(Color.DarkMagenta), Color.DarkMagenta }, + { nameof(Color.DarkOliveGreen), Color.DarkOliveGreen }, + { nameof(Color.DarkOrange), Color.DarkOrange }, + { nameof(Color.DarkOrchid), Color.DarkOrchid }, + { nameof(Color.DarkRed), Color.DarkRed }, + { nameof(Color.DarkSalmon), Color.DarkSalmon }, + { nameof(Color.DarkSeaGreen), Color.DarkSeaGreen }, + { nameof(Color.DarkSlateBlue), Color.DarkSlateBlue }, + { nameof(Color.DarkSlateGray), Color.DarkSlateGray }, + { nameof(Color.DarkSlateGrey), Color.DarkSlateGrey }, + { nameof(Color.DarkTurquoise), Color.DarkTurquoise }, + { nameof(Color.DarkViolet), Color.DarkViolet }, + { nameof(Color.DeepPink), Color.DeepPink }, + { nameof(Color.DeepSkyBlue), Color.DeepSkyBlue }, + { nameof(Color.DimGray), Color.DimGray }, + { nameof(Color.DimGrey), Color.DimGrey }, + { nameof(Color.DodgerBlue), Color.DodgerBlue }, + { nameof(Color.Firebrick), Color.Firebrick }, + { nameof(Color.FloralWhite), Color.FloralWhite }, + { nameof(Color.ForestGreen), Color.ForestGreen }, + { nameof(Color.Fuchsia), Color.Fuchsia }, + { nameof(Color.Gainsboro), Color.Gainsboro }, + { nameof(Color.GhostWhite), Color.GhostWhite }, + { nameof(Color.Gold), Color.Gold }, + { nameof(Color.Goldenrod), Color.Goldenrod }, + { nameof(Color.Gray), Color.Gray }, + { nameof(Color.Green), Color.Green }, + { nameof(Color.GreenYellow), Color.GreenYellow }, + { nameof(Color.Grey), Color.Grey }, + { nameof(Color.Honeydew), Color.Honeydew }, + { nameof(Color.HotPink), Color.HotPink }, + { nameof(Color.IndianRed), Color.IndianRed }, + { nameof(Color.Indigo), Color.Indigo }, + { nameof(Color.Ivory), Color.Ivory }, + { nameof(Color.Khaki), Color.Khaki }, + { nameof(Color.Lavender), Color.Lavender }, + { nameof(Color.LavenderBlush), Color.LavenderBlush }, + { nameof(Color.LawnGreen), Color.LawnGreen }, + { nameof(Color.LemonChiffon), Color.LemonChiffon }, + { nameof(Color.LightBlue), Color.LightBlue }, + { nameof(Color.LightCoral), Color.LightCoral }, + { nameof(Color.LightCyan), Color.LightCyan }, + { nameof(Color.LightGoldenrodYellow), Color.LightGoldenrodYellow }, + { nameof(Color.LightGray), Color.LightGray }, + { nameof(Color.LightGreen), Color.LightGreen }, + { nameof(Color.LightGrey), Color.LightGrey }, + { nameof(Color.LightPink), Color.LightPink }, + { nameof(Color.LightSalmon), Color.LightSalmon }, + { nameof(Color.LightSeaGreen), Color.LightSeaGreen }, + { nameof(Color.LightSkyBlue), Color.LightSkyBlue }, + { nameof(Color.LightSlateGray), Color.LightSlateGray }, + { nameof(Color.LightSlateGrey), Color.LightSlateGrey }, + { nameof(Color.LightSteelBlue), Color.LightSteelBlue }, + { nameof(Color.LightYellow), Color.LightYellow }, + { nameof(Color.Lime), Color.Lime }, + { nameof(Color.LimeGreen), Color.LimeGreen }, + { nameof(Color.Linen), Color.Linen }, + { nameof(Color.Magenta), Color.Magenta }, + { nameof(Color.Maroon), Color.Maroon }, + { nameof(Color.MediumAquamarine), Color.MediumAquamarine }, + { nameof(Color.MediumBlue), Color.MediumBlue }, + { nameof(Color.MediumOrchid), Color.MediumOrchid }, + { nameof(Color.MediumPurple), Color.MediumPurple }, + { nameof(Color.MediumSeaGreen), Color.MediumSeaGreen }, + { nameof(Color.MediumSlateBlue), Color.MediumSlateBlue }, + { nameof(Color.MediumSpringGreen), Color.MediumSpringGreen }, + { nameof(Color.MediumTurquoise), Color.MediumTurquoise }, + { nameof(Color.MediumVioletRed), Color.MediumVioletRed }, + { nameof(Color.MidnightBlue), Color.MidnightBlue }, + { nameof(Color.MintCream), Color.MintCream }, + { nameof(Color.MistyRose), Color.MistyRose }, + { nameof(Color.Moccasin), Color.Moccasin }, + { nameof(Color.NavajoWhite), Color.NavajoWhite }, + { nameof(Color.Navy), Color.Navy }, + { nameof(Color.OldLace), Color.OldLace }, + { nameof(Color.Olive), Color.Olive }, + { nameof(Color.OliveDrab), Color.OliveDrab }, + { nameof(Color.Orange), Color.Orange }, + { nameof(Color.OrangeRed), Color.OrangeRed }, + { nameof(Color.Orchid), Color.Orchid }, + { nameof(Color.PaleGoldenrod), Color.PaleGoldenrod }, + { nameof(Color.PaleGreen), Color.PaleGreen }, + { nameof(Color.PaleTurquoise), Color.PaleTurquoise }, + { nameof(Color.PaleVioletRed), Color.PaleVioletRed }, + { nameof(Color.PapayaWhip), Color.PapayaWhip }, + { nameof(Color.PeachPuff), Color.PeachPuff }, + { nameof(Color.Peru), Color.Peru }, + { nameof(Color.Pink), Color.Pink }, + { nameof(Color.Plum), Color.Plum }, + { nameof(Color.PowderBlue), Color.PowderBlue }, + { nameof(Color.Purple), Color.Purple }, + { nameof(Color.RebeccaPurple), Color.RebeccaPurple }, + { nameof(Color.Red), Color.Red }, + { nameof(Color.RosyBrown), Color.RosyBrown }, + { nameof(Color.RoyalBlue), Color.RoyalBlue }, + { nameof(Color.SaddleBrown), Color.SaddleBrown }, + { nameof(Color.Salmon), Color.Salmon }, + { nameof(Color.SandyBrown), Color.SandyBrown }, + { nameof(Color.SeaGreen), Color.SeaGreen }, + { nameof(Color.SeaShell), Color.SeaShell }, + { nameof(Color.Sienna), Color.Sienna }, + { nameof(Color.Silver), Color.Silver }, + { nameof(Color.SkyBlue), Color.SkyBlue }, + { nameof(Color.SlateBlue), Color.SlateBlue }, + { nameof(Color.SlateGray), Color.SlateGray }, + { nameof(Color.SlateGrey), Color.SlateGrey }, + { nameof(Color.Snow), Color.Snow }, + { nameof(Color.SpringGreen), Color.SpringGreen }, + { nameof(Color.SteelBlue), Color.SteelBlue }, + { nameof(Color.Tan), Color.Tan }, + { nameof(Color.Teal), Color.Teal }, + { nameof(Color.Thistle), Color.Thistle }, + { nameof(Color.Tomato), Color.Tomato }, + { nameof(Color.Transparent), Color.Transparent }, + { nameof(Color.Turquoise), Color.Turquoise }, + { nameof(Color.Violet), Color.Violet }, + { nameof(Color.Wheat), Color.Wheat }, + { nameof(Color.White), Color.White }, + { nameof(Color.WhiteSmoke), Color.WhiteSmoke }, + { nameof(Color.Yellow), Color.Yellow }, + { nameof(Color.YellowGreen), Color.YellowGreen } + }; } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs index 31e63a2fd7..5206910651 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (Image background = provider.GetImage()) using (var overlay = new Image(50, 50)) { - overlay.GetPixelSpan().Fill(Rgba32.Black); + overlay.GetPixelSpan().Fill(Color.Black); background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F)); @@ -184,7 +184,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing public void NonOverlappingImageThrows(TestImageProvider provider, int x, int y) { using (Image background = provider.GetImage()) - using (var overlay = new Image(Configuration.Default, 10, 10, Rgba32.Black)) + using (var overlay = new Image(Configuration.Default, 10, 10, Color.Black)) { ImageProcessingException ex = Assert.Throws(Test); diff --git a/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs index 3cfce6b8e2..fbe259d2bd 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 53% rename from tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs rename to tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 0d30026615..332a141e9d 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -2,25 +2,25 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Concurrent; 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; - using Xunit; using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Helpers { - public class ParallelHelperTests + public class ParallelRowIteratorTests { + public delegate void RowIntervalAction(RowInterval rows, Span span); + private readonly ITestOutputHelper output; - public ParallelHelperTests(ITestOutputHelper output) + public ParallelRowIteratorTests(ITestOutputHelper output) { this.output = output; } @@ -28,18 +28,22 @@ namespace SixLabors.ImageSharp.Tests.Helpers /// /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength /// - public static TheoryData IterateRows_OverMinimumPixelsLimit_Data = - new TheoryData - { - { 1, 0, 100, -1, 100 }, - { 2, 0, 9, 5, 4 }, - { 4, 0, 19, 5, 4 }, - { 2, 10, 19, 5, 4 }, - { 4, 0, 200, 50, 50 }, - { 4, 123, 323, 50, 50 }, - { 4, 0, 1201, 301, 298 }, - { 8, 10, 236, 29, 23 } - }; + 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 }, + }; [Theory] [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] @@ -48,7 +52,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers int minY, int maxY, int expectedStepLength, - int expectedLastStepLength) + int expectedLastStepLength, + int expectedNumberOfSteps) { var parallelSettings = new ParallelExecutionSettings( maxDegreeOfParallelism, @@ -59,22 +64,26 @@ namespace SixLabors.ImageSharp.Tests.Helpers int actualNumberOfSteps = 0; - ParallelHelper.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); + } - Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRows( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } [Theory] @@ -84,7 +93,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers int minY, int maxY, int expectedStepLength, - int expectedLastStepLength) + int expectedLastStepLength, + int expectedNumberOfSteps) { var parallelSettings = new ParallelExecutionSettings( maxDegreeOfParallelism, @@ -96,16 +106,20 @@ 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( + 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); } @@ -117,7 +131,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers int minY, int maxY, int expectedStepLength, - int expectedLastStepLength) + int expectedLastStepLength, + int expectedNumberOfSteps) { var parallelSettings = new ParallelExecutionSettings( maxDegreeOfParallelism, @@ -126,30 +141,28 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rectangle = new Rectangle(0, minY, 10, maxY - minY); - var bufferHashes = new ConcurrentBag(); - int actualNumberOfSteps = 0; - ParallelHelper.IterateRowsWithTempBuffer( - rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); - bufferHashes.Add(buffer.GetHashCode()); + void RowAction(RowInterval rows, Span buffer) + { + 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); - Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); + ParallelRowIterator.IterateRows, Vector4>( + rectangle, + in parallelSettings, + in operation); - int numberOfDifferentBuffers = bufferHashes.Distinct().Count(); - Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers); + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } [Theory] @@ -159,7 +172,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers int minY, int maxY, int expectedStepLength, - int expectedLastStepLength) + int expectedLastStepLength, + int expectedNumberOfSteps) { var parallelSettings = new ParallelExecutionSettings( maxDegreeOfParallelism, @@ -171,31 +185,35 @@ 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( + void RowAction(RowInterval rows, Span 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))] @@ -217,20 +235,24 @@ namespace SixLabors.ImageSharp.Tests.Helpers int actualNumberOfSteps = 0; - ParallelHelper.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); } @@ -254,33 +276,38 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rectangle = new Rectangle(0, 0, width, height); int actualNumberOfSteps = 0; - ParallelHelper.IterateRowsWithTempBuffer( - 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, Span buffer) + { + 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); - }); + 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))] @@ -317,17 +344,21 @@ namespace SixLabors.ImageSharp.Tests.Helpers // Fill actual data using IterateRows: var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); - ParallelHelper.IterateRows( + 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.GetSingleSpan(), actual.GetSingleSpan()); @@ -345,8 +376,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( - () => ParallelHelper.IterateRows(rect, parallelSettings, rows => { })); + () => ParallelRowIterator.IterateRows(rect, in parallelSettings, in operation)); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } @@ -362,10 +399,38 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rect = new Rectangle(0, 0, width, height); + void RowAction(RowInterval rows, Span memory) + { + } + + var operation = new TestRowIntervalOperation(RowAction); + ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelHelper.IterateRowsWithTempBuffer(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 RowIntervalAction action; + + public TestRowIntervalOperation(RowIntervalAction action) + => this.action = action; + + public void Invoke(in RowInterval rows, Span span) + => this.action(rows, span); + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index ea1afa5b62..980898ffa3 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -233,10 +233,10 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CreateFrame_CustomFillColor() { - this.Image.Frames.CreateFrame(Rgba32.HotPink); + this.Image.Frames.CreateFrame(Color.HotPink); Assert.Equal(2, this.Image.Frames.Count); - this.Image.Frames[1].ComparePixelBufferTo(Rgba32.HotPink); + this.Image.Frames[1].ComparePixelBufferTo(Color.HotPink); } [Fact] diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs index 415c1af19f..08f0de38c5 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests } Rgba32[] expectedAllBlue = - Enumerable.Repeat(Rgba32.Blue, this.Image.Width * this.Image.Height).ToArray(); + Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); Assert.Equal(2, this.Collection.Count); var actualFrame = (ImageFrame)this.Collection[1]; @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests } Rgba32[] expectedAllBlue = - Enumerable.Repeat(Rgba32.Blue, this.Image.Width * this.Image.Height).ToArray(); + Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); Assert.Equal(2, this.Collection.Count); var actualFrame = (ImageFrame)this.Collection[0]; @@ -201,13 +201,13 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CreateFrame_CustomFillColor() { - this.Image.Frames.CreateFrame(Rgba32.HotPink); + this.Image.Frames.CreateFrame(Color.HotPink); Assert.Equal(2, this.Image.Frames.Count); var frame = (ImageFrame)this.Image.Frames[1]; - frame.ComparePixelBufferTo(Rgba32.HotPink); + frame.ComparePixelBufferTo(Color.HotPink); } [Fact] diff --git a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs index 7a5fa87290..399652851c 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.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; @@ -16,18 +16,18 @@ namespace SixLabors.ImageSharp.Tests [InlineData(true)] public void FromPixels(bool useSpan) { - Rgba32[] data = { Rgba32.Black, Rgba32.White, Rgba32.White, Rgba32.Black, }; + Rgba32[] data = { Color.Black, Color.White, Color.White, Color.Black, }; using (Image img = useSpan ? Image.LoadPixelData(data.AsSpan(), 2, 2) : Image.LoadPixelData(data, 2, 2)) { Assert.NotNull(img); - Assert.Equal(Rgba32.Black, img[0, 0]); - Assert.Equal(Rgba32.White, img[0, 1]); + Assert.Equal(Color.Black, (Color)img[0, 0]); + Assert.Equal(Color.White, (Color)img[0, 1]); - Assert.Equal(Rgba32.White, img[1, 0]); - Assert.Equal(Rgba32.Black, img[1, 1]); + Assert.Equal(Color.White, (Color)img[1, 0]); + Assert.Equal(Color.Black, (Color)img[1, 1]); } } @@ -48,13 +48,13 @@ namespace SixLabors.ImageSharp.Tests : Image.LoadPixelData(data, 2, 2)) { Assert.NotNull(img); - Assert.Equal(Rgba32.Black, img[0, 0]); - Assert.Equal(Rgba32.White, img[0, 1]); + Assert.Equal(Color.Black, (Color)img[0, 0]); + Assert.Equal(Color.White, (Color)img[0, 1]); - Assert.Equal(Rgba32.White, img[1, 0]); - Assert.Equal(Rgba32.Black, img[1, 1]); + Assert.Equal(Color.White, (Color)img[1, 0]); + Assert.Equal(Color.Black, (Color)img[1, 1]); } } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 99bdfceccb..0fa917972f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests public void Configuration_Width_Height_BackgroundColor() { Configuration configuration = Configuration.Default.Clone(); - Rgba32 color = Rgba32.Aquamarine; + Rgba32 color = Color.Aquamarine; using (var image = new Image(configuration, 11, 23, color)) { diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index 2a37ff8970..4c23e4955f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -45,15 +45,15 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public static TheoryData ColorBlendingExpectedResults = new TheoryData { - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Normal, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Normal, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) }, }; [Theory] @@ -69,18 +69,18 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public static TheoryData AlphaCompositionExpectedResults = new TheoryData { - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Rgba32.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Color.MidnightBlue }, }; [Theory] diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs index a242dba41a..6656ba19c2 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs @@ -21,10 +21,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { var color1 = new Rgba32(0, 0, 0); var color2 = new Rgba32(0, 0, 0, 1F); - var color3 = Rgba32.FromHex("#000"); - var color4 = Rgba32.FromHex("#000F"); - var color5 = Rgba32.FromHex("#000000"); - var color6 = Rgba32.FromHex("#000000FF"); + var color3 = Rgba32.ParseHex("#000"); + var color4 = Rgba32.ParseHex("#000F"); + var color5 = Rgba32.ParseHex("#000000"); + var color6 = Rgba32.ParseHex("#000000FF"); Assert.Equal(color1, color2); Assert.Equal(color1, color3); @@ -41,9 +41,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { var color1 = new Rgba32(255, 0, 0, 255); var color2 = new Rgba32(0, 0, 0, 255); - var color3 = Rgba32.FromHex("#000"); - var color4 = Rgba32.FromHex("#000000"); - var color5 = Rgba32.FromHex("#FF000000"); + var color3 = Rgba32.ParseHex("#000"); + var color4 = Rgba32.ParseHex("#000000"); + var color5 = Rgba32.ParseHex("#FF000000"); Assert.NotEqual(color1, color2); Assert.NotEqual(color1, color3); @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void FromAndToHex() { // 8 digit hex matches css4 spec. RRGGBBAA - var color = Rgba32.FromHex("#AABBCCDD"); // 170, 187, 204, 221 + var color = Rgba32.ParseHex("#AABBCCDD"); // 170, 187, 204, 221 Assert.Equal(170, color.R); Assert.Equal(187, color.G); Assert.Equal(204, color.B); diff --git a/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs index bd8c647421..162775a25f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Colors [Fact] public void Color_Types_From_Hex_Produce_Equal_Scaled_Component_OutPut() { - var color = Rgba32.FromHex("183060C0"); + var color = Rgba32.ParseHex("183060C0"); var colorVector = RgbaVector.FromHex("183060C0"); Assert.Equal(color.R, (byte)(colorVector.R * 255)); diff --git a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs index 100734f30f..ea000ae2a6 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays [Fact] public void Glow_Color_GlowProcessorWithDefaultValues() { - this.operations.Glow(Rgba32.Aquamarine); + this.operations.Glow(Color.Aquamarine); GlowProcessor p = this.Verify(); Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index f2e1136ca6..2e9dc83ddc 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.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 SixLabors.ImageSharp.PixelFormats; @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { public class PaletteQuantizerTests { - private static readonly Color[] Rgb = new Color[] { Rgba32.Red, Rgba32.Green, Rgba32.Blue }; + private static readonly Color[] Rgb = new Color[] { Color.Red, Color.Green, Color.Blue }; [Fact] public void PaletteQuantizerConstructor() diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index ad592f9712..7c30256863 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms } [Theory] - [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Rgba32.Red), PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Color.Red), PixelTypes.Rgba32)] public void Transform_WithTaperMatrix(TestImageProvider provider, TaperSide taperSide, TaperCorner taperCorner) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index 1b0253147a..c83adea91d 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -17,13 +17,13 @@ namespace SixLabors.ImageSharp.Tests.Quantization Configuration config = Configuration.Default; var quantizer = new WuQuantizer(false); - using (var image = new Image(config, 1, 1, Rgba32.Black)) + using (var image = new Image(config, 1, 1, Color.Black)) using (IQuantizedFrame result = quantizer.CreateFrameQuantizer(config).QuantizeFrame(image.Frames[0])) { Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.GetPixelSpan().Length); - Assert.Equal(Rgba32.Black, result.Palette.Span[0]); + Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); Assert.Equal(0, result.GetPixelSpan()[0]); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 26620c45e4..eed9bdd3fd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -157,9 +157,9 @@ namespace SixLabors.ImageSharp.Tests int bottom = pixels.Height; int height = (int)Math.Ceiling(pixels.Height / 6f); - var red = Rgba32.Red.ToVector4(); // use real color so we can see har it translates in the test pattern - var green = Rgba32.Green.ToVector4(); // use real color so we can see har it translates in the test pattern - var blue = Rgba32.Blue.ToVector4(); // use real color so we can see har it translates in the test pattern + var red = Color.Red.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern + var green = Color.Green.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern + var blue = Color.Blue.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern var c = default(TPixel); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 12321d38f3..cea0573302 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 System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -711,25 +710,43 @@ namespace SixLabors.ImageSharp.Tests { Rectangle sourceRectangle = this.SourceRectangle; Configuration configuration = this.Configuration; - ParallelHelper.IterateRowsWithTempBuffer( - 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, Span 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, span, PixelConversionModifiers.Scale); + for (int i = 0; i < span.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 span[i]; + v.W = 1F; + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); + } + } } } }