Browse Source

Merge branch 'master' into af/disco-buffers

af/octree-no-pixelmap
James Jackson-South 6 years ago
parent
commit
b0909e4553
  1. 21
      src/ImageSharp/Advanced/IRowIntervalOperation.cs
  2. 25
      src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs
  3. 8
      src/ImageSharp/Advanced/ParallelExecutionSettings.cs
  4. 110
      src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
  5. 162
      src/ImageSharp/Advanced/ParallelRowIterator.cs
  6. 160
      src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
  7. 200
      src/ImageSharp/Color/Color.NamedColors.cs
  8. 222
      src/ImageSharp/Color/Color.WernerPalette.cs
  9. 91
      src/ImageSharp/Color/Color.cs
  10. 14
      src/ImageSharp/Common/Extensions/EnumerableExtensions.cs
  11. 51
      src/ImageSharp/ImageFrame{TPixel}.cs
  12. 278
      src/ImageSharp/PixelFormats/ColorConstants.cs
  13. 721
      src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs
  14. 42
      src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs
  15. 2
      src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs
  16. 4
      src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs
  17. 2
      src/ImageSharp/Primitives/Rectangle.cs
  18. 84
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
  19. 395
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
  20. 7
      src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
  21. 164
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
  22. 175
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  23. 149
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
  24. 7
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs
  25. 133
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  26. 7
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
  27. 7
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
  28. 68
      src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
  29. 21
      src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs
  30. 205
      src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
  31. 30
      src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs
  32. 70
      src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs
  33. 104
      src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs
  34. 39
      src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel}.cs
  35. 117
      src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs
  36. 30
      src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs
  37. 36
      src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor{TPixel}.cs
  38. 65
      src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs
  39. 302
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs
  40. 425
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
  41. 169
      src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs
  42. 114
      src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs
  43. 131
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs
  44. 140
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs
  45. 56
      src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs
  46. 176
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
  47. 61
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
  48. 55
      src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs
  49. 176
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
  50. 4
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  51. 94
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  52. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  53. 224
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs
  54. 1
      tests/Directory.Build.targets
  55. 4
      tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs
  56. 22
      tests/ImageSharp.Benchmarks/Config.cs
  57. 1
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  58. 1
      tests/ImageSharp.Benchmarks/Samplers/Crop.cs
  59. 2
      tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs
  60. 2
      tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs
  61. 2
      tests/ImageSharp.Benchmarks/Samplers/Rotate.cs
  62. 2
      tests/ImageSharp.Benchmarks/Samplers/Skew.cs
  63. 138
      tests/ImageSharp.Tests/Color/ColorTests.cs
  64. 665
      tests/ImageSharp.Tests/Color/ReferencePalette.cs
  65. 4
      tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
  66. 2
      tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs
  67. 307
      tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs
  68. 4
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
  69. 8
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
  70. 22
      tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs
  71. 2
      tests/ImageSharp.Tests/Image/ImageTests.cs
  72. 42
      tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs
  73. 16
      tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs
  74. 2
      tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs
  75. 2
      tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs
  76. 4
      tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs
  77. 2
      tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
  78. 4
      tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs
  79. 6
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs
  80. 53
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

21
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
{
/// <summary>
/// Defines the contract for an action that operates on a row interval.
/// </summary>
public interface IRowIntervalOperation
{
/// <summary>
/// Invokes the method passing the row interval.
/// </summary>
/// <param name="rows">The row interval.</param>
void Invoke(in RowInterval rows);
}
}

25
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
{
/// <summary>
/// Defines the contract for an action that operates on a row interval with a temporary buffer.
/// </summary>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
public interface IRowIntervalOperation<TBuffer>
where TBuffer : unmanaged
{
/// <summary>
/// Invokes the method passing the row interval and a buffer.
/// </summary>
/// <param name="rows">The row interval.</param>
/// <param name="span">The contiguous region of memory.</param>
void Invoke(in RowInterval rows, Span<TBuffer> span);
}
}

8
src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs → 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
{
/// <summary>
/// Defines execution settings for methods in <see cref="ParallelHelper"/>.
/// Defines execution settings for methods in <see cref="ParallelRowIterator"/>.
/// </summary>
public readonly struct ParallelExecutionSettings
{
@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
}
/// <summary>
/// Get the default <see cref="SixLabors.ImageSharp.Advanced.ParallelUtils.ParallelExecutionSettings"/> for a <see cref="SixLabors.ImageSharp.Configuration"/>
/// Get the default <see cref="SixLabors.ImageSharp.Advanced.ParallelExecutionSettings"/> for a <see cref="SixLabors.ImageSharp.Configuration"/>
/// </summary>
/// <param name="configuration">The <see cref="Configuration"/>.</param>
/// <returns>The <see cref="ParallelExecutionSettings"/>.</returns>

110
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
{
/// <content>
/// Utility methods for batched processing of pixel row intervals.
/// Parallel execution is optimized for image processing based on values defined
/// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>.
/// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods.
/// </content>
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<T>
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<T, TBuffer>
where T : struct, IRowIntervalOperation<TBuffer>
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<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.info.Width);
Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span);
}
}
}
}

162
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
{
/// <summary>
/// Utility methods for batched processing of pixel row intervals.
/// Parallel execution is optimized for image processing based on values defined
/// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>.
/// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods.
/// </summary>
public static partial class ParallelRowIterator
{
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void IterateRows<T>(Configuration configuration, Rectangle rectangle, in T operation)
where T : struct, IRowIntervalOperation
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, in parallelSettings, in operation);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows<T>(
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<T>(in info, in operation);
Parallel.For(
0,
numOfSteps,
parallelOptions,
wrappingOperation.Invoke);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="operation"/> invocation.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows<T, TBuffer>(Configuration configuration, Rectangle rectangle, in T operation)
where T : struct, IRowIntervalOperation<TBuffer>
where TBuffer : unmanaged
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows<T, TBuffer>(rectangle, in parallelSettings, in operation);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="operation"/> invocation.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows<T, TBuffer>(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
in T operation)
where T : struct, IRowIntervalOperation<TBuffer>
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<TBuffer> buffer = allocator.Allocate<TBuffer>(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<T, TBuffer>(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)}");
}
}
}

160
src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs

@ -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
{
/// <summary>
/// Utility methods for batched processing of pixel row intervals.
/// Parallel execution is optimized for image processing based on values defined
/// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>.
/// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods.
/// </summary>
public static class ParallelHelper
{
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows(Rectangle rectangle, Configuration configuration, Action<RowInterval> body)
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, parallelSettings, body);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
Action<RowInterval> 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);
});
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="body"/> invocation.
/// </summary>
internal static void IterateRowsWithTempBuffer<T>(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
Action<RowInterval, Memory<T>> 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<T> buffer = memoryAllocator.Allocate<T>(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<T> buffer = memoryAllocator.Allocate<T>(rectangle.Width))
{
body(rows, buffer.Memory);
}
});
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="body"/> invocation.
/// </summary>
internal static void IterateRowsWithTempBuffer<T>(
Rectangle rectangle,
Configuration configuration,
Action<RowInterval, Memory<T>> 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)}");
}
}
}

200
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
{
/// <content>
@ -8,6 +11,8 @@ namespace SixLabors.ImageSharp
/// </content>
public readonly partial struct Color
{
private static readonly Lazy<Dictionary<string, Color>> NamedColorsLookupLazy = new Lazy<Dictionary<string, Color>>(CreateNamedColorsLookup, true);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0F8FF.
/// </summary>
@ -111,7 +116,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00FFFF.
/// </summary>
public static readonly Color Cyan = FromRgba(0, 255, 255, 255);
public static readonly Color Cyan = Aqua;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00008B.
@ -138,6 +143,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color DarkGreen = FromRgba(0, 100, 0, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #A9A9A9.
/// </summary>
public static readonly Color DarkGrey = DarkGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #BDB76B.
/// </summary>
@ -188,6 +198,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color DarkSlateGray = FromRgba(47, 79, 79, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #2F4F4F.
/// </summary>
public static readonly Color DarkSlateGrey = DarkSlateGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00CED1.
/// </summary>
@ -213,6 +228,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color DimGray = FromRgba(105, 105, 105, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #696969.
/// </summary>
public static readonly Color DimGrey = DimGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #1E90FF.
/// </summary>
@ -273,6 +293,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color GreenYellow = FromRgba(173, 255, 47, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #808080.
/// </summary>
public static readonly Color Grey = Gray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0FFF0.
/// </summary>
@ -353,6 +378,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color LightGreen = FromRgba(144, 238, 144, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #D3D3D3.
/// </summary>
public static readonly Color LightGrey = LightGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFB6C1.
/// </summary>
@ -378,6 +408,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color LightSlateGray = FromRgba(119, 136, 153, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #778899.
/// </summary>
public static readonly Color LightSlateGrey = LightSlateGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #B0C4DE.
/// </summary>
@ -406,7 +441,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF00FF.
/// </summary>
public static readonly Color Magenta = FromRgba(255, 0, 255, 255);
public static readonly Color Magenta = Fuchsia;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #800000.
@ -643,6 +678,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color SlateGray = FromRgba(112, 128, 144, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #708090.
/// </summary>
public static readonly Color SlateGrey = SlateGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFAFA.
/// </summary>
@ -717,5 +757,161 @@ namespace SixLabors.ImageSharp
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #9ACD32.
/// </summary>
public static readonly Color YellowGreen = FromRgba(154, 205, 50, 255);
private static Dictionary<string, Color> CreateNamedColorsLookup()
{
return new Dictionary<string, Color>(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 }
};
}
}
}

222
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")
};
}
}
}

91
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);
/// <summary>
/// Creates a new <see cref="Color"/> instance from the string representing a color in hexadecimal form.
/// Creates a new instance of the <see cref="Color"/> struct
/// from the given hexadecimal string.
/// </summary>
/// <param name="hex">
/// The hexadecimal representation of the combined color components arranged
/// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
/// </param>
/// <returns>Returns a <see cref="Color"/> that represents the color defined by the provided RGBA hex string.</returns>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
[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);
}
/// <summary>
/// Attempts to creates a new instance of the <see cref="Color"/> struct
/// from the given hexadecimal string.
/// </summary>
/// <param name="hex">
/// The hexadecimal representation of the combined color components arranged
/// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
/// </param>
/// <param name="result">When this method returns, contains the <see cref="Color"/> equivalent of the hexadecimal input.</param>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
[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;
}
/// <summary>
/// Creates a new instance of the <see cref="Color"/> struct
/// from the given input string.
/// </summary>
/// <param name="input">
/// 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.
/// </param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
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;
}
/// <summary>
/// Attempts to creates a new instance of the <see cref="Color"/> struct
/// from the given input string.
/// </summary>
/// <param name="input">
/// 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.
/// </param>
/// <param name="result">When this method returns, contains the <see cref="Color"/> equivalent of the hexadecimal input.</param>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
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);
}
/// <summary>
/// Alters the alpha channel of the color, returning a new instance.
/// </summary>
@ -117,7 +198,7 @@ namespace SixLabors.ImageSharp
/// <returns>The color having it's alpha channel altered.</returns>
public Color WithAlpha(float alpha)
{
Vector4 v = (Vector4)this;
var v = (Vector4)this;
v.W = alpha;
return new Color(v);
}

14
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
{
/// <summary>
/// Encapsulates a series of time saving extension methods to the <see cref="T:System.Collections.IEnumerable"/> interface.
@ -34,15 +34,11 @@ namespace SixLabors.ImageSharp.Common
/// <summary>
/// Generates a sequence of integral numbers within a specified range.
/// </summary>
/// <param name="fromInclusive">
/// The start index, inclusive.
/// </param>
/// <param name="fromInclusive">The start index, inclusive.</param>
/// <param name="toDelegate">
/// A method that has one parameter and returns a <see cref="bool"/> calculating the end index.
/// </param>
/// <param name="step">
/// The incremental step.
/// </param>
/// <param name="step">The incremental step.</param>
/// <returns>
/// The <see cref="IEnumerable{Int32}"/> that contains a range of sequential integral numbers.
/// </returns>
@ -56,4 +52,4 @@ namespace SixLabors.ImageSharp.Common
}
}
}
}
}

51
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<TPixel2>(configuration, this.Width, this.Height, this.Metadata.DeepClone());
var operation = new RowIntervalOperation<TPixel2>(this, target, configuration);
ParallelHelper.IterateRows(
this.Bounds(),
ParallelRowIterator.IterateRows(
configuration,
rows =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRow = this.GetPixelRowSpan(y);
Span<TPixel2> targetRow = target.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.To(configuration, sourceRow, targetRow);
}
});
this.Bounds(),
in operation);
return target;
}
@ -295,5 +286,39 @@ namespace SixLabors.ImageSharp
group.Fill(value);
}
}
/// <summary>
/// A <see langword="struct"/> implementing the clone logic for <see cref="ImageFrame{TPixel}"/>.
/// </summary>
private readonly struct RowIntervalOperation<TPixel2> : IRowIntervalOperation
where TPixel2 : struct, IPixel<TPixel2>
{
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel2> target;
private readonly Configuration configuration;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
ImageFrame<TPixel> source,
ImageFrame<TPixel2> target,
Configuration configuration)
{
this.source = source;
this.target = target;
this.configuration = configuration;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
Span<TPixel2> targetRow = this.target.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.To(this.configuration, sourceRow, targetRow);
}
}
}
}
}

278
src/ImageSharp/PixelFormats/ColorConstants.cs

@ -1,278 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.PixelFormats
{
/// <summary>
/// Provides useful color definitions.
/// </summary>
public static class ColorConstants
{
/// <summary>
/// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4.
/// </summary>
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
};
/// <summary>
/// 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 <see href="https://www.c82.net/werner"/>
/// </summary>
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")
};
}
}

721
src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs

@ -1,721 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.PixelFormats
{
/// <content>
/// Provides standardized definitions for named colors.
/// </content>
public partial struct Rgba32
{
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #F0F8FF.
/// </summary>
public static readonly Rgba32 AliceBlue = Color.AliceBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FAEBD7.
/// </summary>
public static readonly Rgba32 AntiqueWhite = Color.AntiqueWhite;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #00FFFF.
/// </summary>
public static readonly Rgba32 Aqua = Color.Aqua;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #7FFFD4.
/// </summary>
public static readonly Rgba32 Aquamarine = Color.Aquamarine;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #F0FFFF.
/// </summary>
public static readonly Rgba32 Azure = Color.Azure;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #F5F5DC.
/// </summary>
public static readonly Rgba32 Beige = Color.Beige;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFE4C4.
/// </summary>
public static readonly Rgba32 Bisque = Color.Bisque;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #000000.
/// </summary>
public static readonly Rgba32 Black = Color.Black;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFEBCD.
/// </summary>
public static readonly Rgba32 BlanchedAlmond = Color.BlanchedAlmond;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #0000FF.
/// </summary>
public static readonly Rgba32 Blue = Color.Blue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #8A2BE2.
/// </summary>
public static readonly Rgba32 BlueViolet = Color.BlueViolet;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #A52A2A.
/// </summary>
public static readonly Rgba32 Brown = Color.Brown;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #DEB887.
/// </summary>
public static readonly Rgba32 BurlyWood = Color.BurlyWood;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #5F9EA0.
/// </summary>
public static readonly Rgba32 CadetBlue = Color.CadetBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #7FFF00.
/// </summary>
public static readonly Rgba32 Chartreuse = Color.Chartreuse;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #D2691E.
/// </summary>
public static readonly Rgba32 Chocolate = Color.Chocolate;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FF7F50.
/// </summary>
public static readonly Rgba32 Coral = Color.Coral;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #6495ED.
/// </summary>
public static readonly Rgba32 CornflowerBlue = Color.CornflowerBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFF8DC.
/// </summary>
public static readonly Rgba32 Cornsilk = Color.Cornsilk;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #DC143C.
/// </summary>
public static readonly Rgba32 Crimson = Color.Crimson;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #00FFFF.
/// </summary>
public static readonly Rgba32 Cyan = Color.Cyan;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #00008B.
/// </summary>
public static readonly Rgba32 DarkBlue = Color.DarkBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #008B8B.
/// </summary>
public static readonly Rgba32 DarkCyan = Color.DarkCyan;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #B8860B.
/// </summary>
public static readonly Rgba32 DarkGoldenrod = Color.DarkGoldenrod;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #A9A9A9.
/// </summary>
public static readonly Rgba32 DarkGray = Color.DarkGray;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #006400.
/// </summary>
public static readonly Rgba32 DarkGreen = Color.DarkGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #BDB76B.
/// </summary>
public static readonly Rgba32 DarkKhaki = Color.DarkKhaki;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #8B008B.
/// </summary>
public static readonly Rgba32 DarkMagenta = Color.DarkMagenta;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #556B2F.
/// </summary>
public static readonly Rgba32 DarkOliveGreen = Color.DarkOliveGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FF8C00.
/// </summary>
public static readonly Rgba32 DarkOrange = Color.DarkOrange;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #9932CC.
/// </summary>
public static readonly Rgba32 DarkOrchid = Color.DarkOrchid;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #8B0000.
/// </summary>
public static readonly Rgba32 DarkRed = Color.DarkRed;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #E9967A.
/// </summary>
public static readonly Rgba32 DarkSalmon = Color.DarkSalmon;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #8FBC8B.
/// </summary>
public static readonly Rgba32 DarkSeaGreen = Color.DarkSeaGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #483D8B.
/// </summary>
public static readonly Rgba32 DarkSlateBlue = Color.DarkSlateBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #2F4F4F.
/// </summary>
public static readonly Rgba32 DarkSlateGray = Color.DarkSlateGray;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #00CED1.
/// </summary>
public static readonly Rgba32 DarkTurquoise = Color.DarkTurquoise;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #9400D3.
/// </summary>
public static readonly Rgba32 DarkViolet = Color.DarkViolet;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FF1493.
/// </summary>
public static readonly Rgba32 DeepPink = Color.DeepPink;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #00BFFF.
/// </summary>
public static readonly Rgba32 DeepSkyBlue = Color.DeepSkyBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #696969.
/// </summary>
public static readonly Rgba32 DimGray = Color.DimGray;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #1E90FF.
/// </summary>
public static readonly Rgba32 DodgerBlue = Color.DodgerBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #B22222.
/// </summary>
public static readonly Rgba32 Firebrick = Color.Firebrick;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFFAF0.
/// </summary>
public static readonly Rgba32 FloralWhite = Color.FloralWhite;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #228B22.
/// </summary>
public static readonly Rgba32 ForestGreen = Color.ForestGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FF00FF.
/// </summary>
public static readonly Rgba32 Fuchsia = Color.Fuchsia;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #DCDCDC.
/// </summary>
public static readonly Rgba32 Gainsboro = Color.Gainsboro;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #F8F8FF.
/// </summary>
public static readonly Rgba32 GhostWhite = Color.GhostWhite;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFD700.
/// </summary>
public static readonly Rgba32 Gold = Color.Gold;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #DAA520.
/// </summary>
public static readonly Rgba32 Goldenrod = Color.Goldenrod;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #808080.
/// </summary>
public static readonly Rgba32 Gray = Color.Gray;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #008000.
/// </summary>
public static readonly Rgba32 Green = Color.Green;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #ADFF2F.
/// </summary>
public static readonly Rgba32 GreenYellow = Color.GreenYellow;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #F0FFF0.
/// </summary>
public static readonly Rgba32 Honeydew = Color.Honeydew;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FF69B4.
/// </summary>
public static readonly Rgba32 HotPink = Color.HotPink;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #CD5C5C.
/// </summary>
public static readonly Rgba32 IndianRed = Color.IndianRed;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #4B0082.
/// </summary>
public static readonly Rgba32 Indigo = Color.Indigo;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFFFF0.
/// </summary>
public static readonly Rgba32 Ivory = Color.Ivory;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #F0E68C.
/// </summary>
public static readonly Rgba32 Khaki = Color.Khaki;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #E6E6FA.
/// </summary>
public static readonly Rgba32 Lavender = Color.Lavender;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFF0F5.
/// </summary>
public static readonly Rgba32 LavenderBlush = Color.LavenderBlush;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #7CFC00.
/// </summary>
public static readonly Rgba32 LawnGreen = Color.LawnGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFFACD.
/// </summary>
public static readonly Rgba32 LemonChiffon = Color.LemonChiffon;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #ADD8E6.
/// </summary>
public static readonly Rgba32 LightBlue = Color.LightBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #F08080.
/// </summary>
public static readonly Rgba32 LightCoral = Color.LightCoral;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #E0FFFF.
/// </summary>
public static readonly Rgba32 LightCyan = Color.LightCyan;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FAFAD2.
/// </summary>
public static readonly Rgba32 LightGoldenrodYellow = Color.LightGoldenrodYellow;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #D3D3D3.
/// </summary>
public static readonly Rgba32 LightGray = Color.LightGray;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #90EE90.
/// </summary>
public static readonly Rgba32 LightGreen = Color.LightGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFB6C1.
/// </summary>
public static readonly Rgba32 LightPink = Color.LightPink;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFA07A.
/// </summary>
public static readonly Rgba32 LightSalmon = Color.LightSalmon;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #20B2AA.
/// </summary>
public static readonly Rgba32 LightSeaGreen = Color.LightSeaGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #87CEFA.
/// </summary>
public static readonly Rgba32 LightSkyBlue = Color.LightSkyBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #778899.
/// </summary>
public static readonly Rgba32 LightSlateGray = Color.LightSlateGray;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #B0C4DE.
/// </summary>
public static readonly Rgba32 LightSteelBlue = Color.LightSteelBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFFFE0.
/// </summary>
public static readonly Rgba32 LightYellow = Color.LightYellow;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #00FF00.
/// </summary>
public static readonly Rgba32 Lime = Color.Lime;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #32CD32.
/// </summary>
public static readonly Rgba32 LimeGreen = Color.LimeGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FAF0E6.
/// </summary>
public static readonly Rgba32 Linen = Color.Linen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FF00FF.
/// </summary>
public static readonly Rgba32 Magenta = Color.Magenta;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #800000.
/// </summary>
public static readonly Rgba32 Maroon = Color.Maroon;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #66CDAA.
/// </summary>
public static readonly Rgba32 MediumAquamarine = Color.MediumAquamarine;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #0000CD.
/// </summary>
public static readonly Rgba32 MediumBlue = Color.MediumBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #BA55D3.
/// </summary>
public static readonly Rgba32 MediumOrchid = Color.MediumOrchid;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #9370DB.
/// </summary>
public static readonly Rgba32 MediumPurple = Color.MediumPurple;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #3CB371.
/// </summary>
public static readonly Rgba32 MediumSeaGreen = Color.MediumSeaGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #7B68EE.
/// </summary>
public static readonly Rgba32 MediumSlateBlue = Color.MediumSlateBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #00FA9A.
/// </summary>
public static readonly Rgba32 MediumSpringGreen = Color.MediumSpringGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #48D1CC.
/// </summary>
public static readonly Rgba32 MediumTurquoise = Color.MediumTurquoise;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #C71585.
/// </summary>
public static readonly Rgba32 MediumVioletRed = Color.MediumVioletRed;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #191970.
/// </summary>
public static readonly Rgba32 MidnightBlue = Color.MidnightBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #F5FFFA.
/// </summary>
public static readonly Rgba32 MintCream = Color.MintCream;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFE4E1.
/// </summary>
public static readonly Rgba32 MistyRose = Color.MistyRose;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFE4B5.
/// </summary>
public static readonly Rgba32 Moccasin = Color.Moccasin;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFDEAD.
/// </summary>
public static readonly Rgba32 NavajoWhite = Color.NavajoWhite;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #000080.
/// </summary>
public static readonly Rgba32 Navy = Color.Navy;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FDF5E6.
/// </summary>
public static readonly Rgba32 OldLace = Color.OldLace;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #808000.
/// </summary>
public static readonly Rgba32 Olive = Color.Olive;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #6B8E23.
/// </summary>
public static readonly Rgba32 OliveDrab = Color.OliveDrab;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFA500.
/// </summary>
public static readonly Rgba32 Orange = Color.Orange;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FF4500.
/// </summary>
public static readonly Rgba32 OrangeRed = Color.OrangeRed;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #DA70D6.
/// </summary>
public static readonly Rgba32 Orchid = Color.Orchid;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #EEE8AA.
/// </summary>
public static readonly Rgba32 PaleGoldenrod = Color.PaleGoldenrod;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #98FB98.
/// </summary>
public static readonly Rgba32 PaleGreen = Color.PaleGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #AFEEEE.
/// </summary>
public static readonly Rgba32 PaleTurquoise = Color.PaleTurquoise;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #DB7093.
/// </summary>
public static readonly Rgba32 PaleVioletRed = Color.PaleVioletRed;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFEFD5.
/// </summary>
public static readonly Rgba32 PapayaWhip = Color.PapayaWhip;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFDAB9.
/// </summary>
public static readonly Rgba32 PeachPuff = Color.PeachPuff;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #CD853F.
/// </summary>
public static readonly Rgba32 Peru = Color.Peru;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFC0CB.
/// </summary>
public static readonly Rgba32 Pink = Color.Pink;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #DDA0DD.
/// </summary>
public static readonly Rgba32 Plum = Color.Plum;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #B0E0E6.
/// </summary>
public static readonly Rgba32 PowderBlue = Color.PowderBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #800080.
/// </summary>
public static readonly Rgba32 Purple = Color.Purple;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #663399.
/// </summary>
public static readonly Rgba32 RebeccaPurple = Color.RebeccaPurple;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FF0000.
/// </summary>
public static readonly Rgba32 Red = Color.Red;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #BC8F8F.
/// </summary>
public static readonly Rgba32 RosyBrown = Color.RosyBrown;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #4169E1.
/// </summary>
public static readonly Rgba32 RoyalBlue = Color.RoyalBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #8B4513.
/// </summary>
public static readonly Rgba32 SaddleBrown = Color.SaddleBrown;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FA8072.
/// </summary>
public static readonly Rgba32 Salmon = Color.Salmon;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #F4A460.
/// </summary>
public static readonly Rgba32 SandyBrown = Color.SandyBrown;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #2E8B57.
/// </summary>
public static readonly Rgba32 SeaGreen = Color.SeaGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFF5EE.
/// </summary>
public static readonly Rgba32 SeaShell = Color.SeaShell;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #A0522D.
/// </summary>
public static readonly Rgba32 Sienna = Color.Sienna;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #C0C0C0.
/// </summary>
public static readonly Rgba32 Silver = Color.Silver;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #87CEEB.
/// </summary>
public static readonly Rgba32 SkyBlue = Color.SkyBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #6A5ACD.
/// </summary>
public static readonly Rgba32 SlateBlue = Color.SlateBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #708090.
/// </summary>
public static readonly Rgba32 SlateGray = Color.SlateGray;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFFAFA.
/// </summary>
public static readonly Rgba32 Snow = Color.Snow;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #00FF7F.
/// </summary>
public static readonly Rgba32 SpringGreen = Color.SpringGreen;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #4682B4.
/// </summary>
public static readonly Rgba32 SteelBlue = Color.SteelBlue;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #D2B48C.
/// </summary>
public static readonly Rgba32 Tan = Color.Tan;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #008080.
/// </summary>
public static readonly Rgba32 Teal = Color.Teal;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #D8BFD8.
/// </summary>
public static readonly Rgba32 Thistle = Color.Thistle;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FF6347.
/// </summary>
public static readonly Rgba32 Tomato = Color.Tomato;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFFFFF.
/// </summary>
public static readonly Rgba32 Transparent = Color.Transparent;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #40E0D0.
/// </summary>
public static readonly Rgba32 Turquoise = Color.Turquoise;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #EE82EE.
/// </summary>
public static readonly Rgba32 Violet = Color.Violet;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #F5DEB3.
/// </summary>
public static readonly Rgba32 Wheat = Color.Wheat;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFFFFF.
/// </summary>
public static readonly Rgba32 White = Color.White;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #F5F5F5.
/// </summary>
public static readonly Rgba32 WhiteSmoke = Color.WhiteSmoke;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #FFFF00.
/// </summary>
public static readonly Rgba32 Yellow = Color.Yellow;
/// <summary>
/// Represents a <see cref="Rgba32"/> matching the W3C definition that has an hex value of #9ACD32.
/// </summary>
public static readonly Rgba32 YellowGreen = Color.YellowGreen;
}
}

42
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);
/// <summary>
/// Creates a new instance of the <see cref="Rgba32"/> struct.
/// Creates a new instance of the <see cref="Rgba32"/> struct
/// from the given hexadecimal string.
/// </summary>
/// <param name="hex">
/// The hexadecimal representation of the combined color components arranged
@ -239,19 +240,50 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <returns>
/// The <see cref="Rgba32"/>.
/// </returns>
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;
}
/// <summary>
/// Attempts to creates a new instance of the <see cref="Rgba32"/> struct
/// from the given hexadecimal string.
/// </summary>
/// <param name="hex">
/// The hexadecimal representation of the combined color components arranged
/// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
/// </param>
/// <param name="result">When this method returns, contains the <see cref="Rgba32"/> equivalent of the hexadecimal input.</param>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
[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<uint, Rgba32>(ref packedValue);
result = Unsafe.As<uint, Rgba32>(ref packedValue);
return true;
}
/// <inheritdoc />

2
src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs

@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <returns>
/// The <see cref="RgbaVector"/>.
/// </returns>
public static RgbaVector FromHex(string hex) => Color.FromHex(hex).ToPixel<RgbaVector>();
public static RgbaVector FromHex(string hex) => Color.ParseHex(hex).ToPixel<RgbaVector>();
/// <inheritdoc />
public PixelOperations<RgbaVector> CreatePixelOperations() => new PixelOperations();

4
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
}
}
}
}
}

2
src/ImageSharp/Primitives/Rectangle.cs

@ -460,4 +460,4 @@ namespace SixLabors.ImageSharp
this.Width.Equals(other.Width) &&
this.Height.Equals(other.Height);
}
}
}

84
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<TPixel> row = source.GetPixelRowSpan(y);
interest,
in operation);
}
/// <summary>
/// A <see langword="struct"/> implementing the clone logic for <see cref="BinaryThresholdProcessor{TPixel}"/>.
/// </summary>
private readonly struct RowIntervalOperation : IRowIntervalOperation
{
private readonly ImageFrame<TPixel> 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<TPixel> 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;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
Rgba32 rgba = default;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> 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;
}
}
}
}
}
}

395
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<TPixel> 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<ApplyGammaExposureRowIntervalOperation, Vector4>(
this.Configuration,
this.SourceRectangle,
in gammaOperation);
// Create a 0-filled buffer to use to store the result of the component convolutions
using (Buffer2D<Vector4> processingBuffer = this.Configuration.MemoryAllocator.Allocate2D<Vector4>(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<Vector4> processingBuffer = this.Configuration.MemoryAllocator.Allocate2D<Vector4>(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);
}
/// <summary>
@ -295,216 +303,223 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Configuration configuration,
Buffer2D<Vector4> processingBuffer)
{
using (Buffer2D<ComplexVector4> firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size()))
// Allocate the buffer with the intermediate convolution results
using Buffer2D<ComplexVector4> firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D<ComplexVector4>(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);
}
}
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}"/> at the specified location
/// and with the specified size.
/// A <see langword="struct"/> implementing the vertical convolution logic for <see cref="BokehBlurProcessor{T}"/>.
/// </summary>
/// <param name="targetValues">The target <see cref="ComplexVector4"/> values to use to store the results.</param>
/// <param name="sourcePixels">The source pixels. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="kernel">The 1D kernel.</param>
/// <param name="configuration">The <see cref="Configuration"/></param>
private void ApplyConvolution(
Buffer2D<ComplexVector4> targetValues,
Buffer2D<TPixel> 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<ComplexVector4> targetValues;
private readonly Buffer2D<TPixel> sourcePixels;
private readonly Complex64[] kernel;
private readonly int maxY;
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)]
public ApplyVerticalConvolutionRowIntervalOperation(
Rectangle bounds,
Buffer2D<ComplexVector4> targetValues,
Buffer2D<TPixel> 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;
}
/// <inheritdoc/>
[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<ComplexVector4> targetRowSpan = targetValues.GetRowSpanUnchecked(y).Slice(startX);
Span<ComplexVector4> 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);
}
});
}
}
}
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="Buffer2D{T}"/> buffer at the specified location
/// and with the specified size.
/// A <see langword="struct"/> implementing the horizontal convolution logic for <see cref="BokehBlurProcessor{T}"/>.
/// </summary>
/// <param name="targetValues">The target <see cref="Vector4"/> values to use to store the results.</param>
/// <param name="sourceValues">The source complex values. Cannot be null.</param>
/// <param name="sourceRectangle">The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.</param>
/// <param name="kernel">The 1D kernel.</param>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="z">The weight factor for the real component of the complex pixel values.</param>
/// <param name="w">The weight factor for the imaginary component of the complex pixel values.</param>
private void ApplyConvolution(
Buffer2D<Vector4> targetValues,
Buffer2D<ComplexVector4> 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<Vector4> targetValues;
private readonly Buffer2D<ComplexVector4> 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<Vector4> targetValues,
Buffer2D<ComplexVector4> 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;
}
/// <inheritdoc/>
[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<Vector4> targetRowSpan = targetValues.GetRowSpanUnchecked(y).Slice(startX);
Span<Vector4> 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);
}
});
}
}
}
/// <summary>
/// Applies the gamma correction/highlight to the input pixel buffer.
/// A <see langword="struct"/> implementing the gamma exposure logic for <see cref="BokehBlurProcessor{T}"/>.
/// </summary>
/// <param name="targetPixels">The target pixel buffer to adjust.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="configuration">The <see cref="Configuration"/></param>
private void ApplyGammaExposure(
Buffer2D<TPixel> targetPixels,
Rectangle sourceRectangle,
Configuration configuration)
private readonly struct ApplyGammaExposureRowIntervalOperation : IRowIntervalOperation<Vector4>
{
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<Vector4>(
workingRectangle,
configuration,
(rows, vectorBuffer) =>
private readonly Rectangle bounds;
private readonly Buffer2D<TPixel> targetPixels;
private readonly Configuration configuration;
private readonly float gamma;
[MethodImpl(InliningOptions.ShortMethod)]
public ApplyGammaExposureRowIntervalOperation(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Configuration configuration,
float gamma)
{
this.bounds = bounds;
this.targetPixels = targetPixels;
this.configuration = configuration;
this.gamma = gamma;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X);
PixelOperations<TPixel>.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<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX);
PixelOperations<TPixel>.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<TPixel>.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<TPixel>.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
}
}
}
/// <summary>
/// Applies the inverse gamma correction/highlight pass, and converts the input <see cref="Vector4"/> buffer into pixel values.
/// A <see langword="struct"/> implementing the inverse gamma exposure logic for <see cref="BokehBlurProcessor{T}"/>.
/// </summary>
/// <param name="targetPixels">The target pixels to apply the process to.</param>
/// <param name="sourceValues">The source <see cref="Vector4"/> values. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="configuration">The <see cref="Configuration"/></param>
private void ApplyInverseGammaExposure(
Buffer2D<TPixel> targetPixels,
Buffer2D<Vector4> 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<TPixel> targetPixels;
private readonly Buffer2D<Vector4> sourceValues;
private readonly Configuration configuration;
private readonly float inverseGamma;
[MethodImpl(InliningOptions.ShortMethod)]
public ApplyInverseGammaExposureRowIntervalOperation(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Buffer2D<Vector4> sourceValues,
Configuration configuration,
float inverseGamma)
{
this.bounds = bounds;
this.targetPixels = targetPixels;
this.sourceValues = sourceValues;
this.configuration = configuration;
this.inverseGamma = inverseGamma;
}
/// <inheritdoc/>
[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<TPixel> targetPixelSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X);
Span<Vector4> 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<TPixel> targetPixelSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX);
Span<Vector4> 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<TPixel>.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<TPixel>.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply);
}
}
}
}
}

7
src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs

@ -40,10 +40,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using (var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle);
processor.Apply(source);
}
/// <summary>

164
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
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
DenseMatrix<float> matrixY = this.KernelY;
DenseMatrix<float> matrixX = this.KernelX;
bool preserveAlpha = this.PreserveAlpha;
using Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(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<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height))
var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
this.Configuration,
interest,
in operation);
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="Convolution2DProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4>
{
private readonly Rectangle bounds;
private readonly int maxY;
private readonly int maxX;
private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> sourcePixels;
private readonly DenseMatrix<float> kernelY;
private readonly DenseMatrix<float> kernelX;
private readonly Configuration configuration;
private readonly bool preserveAlpha;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
DenseMatrix<float> kernelY,
DenseMatrix<float> 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;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span)
{
ref Vector4 spanRef = ref MemoryMarshal.GetReference(span);
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
workingRectangle,
this.Configuration,
(rows, vectorBuffer) =>
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X);
PixelOperations<TPixel>.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<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX);
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan);
}
});
Buffer2D<TPixel>.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<TPixel>.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
}
}
}
}

175
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
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using (Buffer2D<TPixel> firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(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<TPixel> firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(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<RowIntervalOperation, Vector4>(
this.Configuration,
interest,
in horizontalOperation);
// Vertical convolution
var verticalOperation = new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
this.Configuration,
interest,
in verticalOperation);
}
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}"/> at the specified location
/// and with the specified size.
/// A <see langword="struct"/> implementing the convolution logic for <see cref="Convolution2PassProcessor{T}"/>.
/// </summary>
/// <param name="targetPixels">The target pixels to apply the process to.</param>
/// <param name="sourcePixels">The source pixels. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="kernel">The kernel operator.</param>
/// <param name="configuration">The <see cref="Configuration"/></param>
private void ApplyConvolution(
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
Rectangle sourceRectangle,
in DenseMatrix<float> kernel,
Configuration configuration)
private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4>
{
DenseMatrix<float> 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<Vector4>(
workingRectangle,
configuration,
(rows, vectorBuffer) =>
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
private readonly Rectangle bounds;
private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> sourcePixels;
private readonly DenseMatrix<float> kernel;
private readonly Configuration configuration;
private readonly bool preserveAlpha;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
DenseMatrix<float> 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++)
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> 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<TPixel> targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
if (this.preserveAlpha)
{
for (int x = 0; x < this.bounds.Width; x++)
{
Span<TPixel> targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX);
PixelOperations<TPixel>.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<TPixel>.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<TPixel>.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
}
}
}
}
}

149
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
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
DenseMatrix<float> matrix = this.KernelXY;
bool preserveAlpha = this.PreserveAlpha;
using Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(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<RowIntervalOperation, Vector4>(
this.Configuration,
interest,
in operation);
using (Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size()))
{
source.CopyTo(targetPixels);
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
int width = workingRectangle.Width;
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="ConvolutionProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4>
{
private readonly Rectangle bounds;
private readonly int maxY;
private readonly int maxX;
private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> sourcePixels;
private readonly DenseMatrix<float> kernel;
private readonly Configuration configuration;
private readonly bool preserveAlpha;
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
workingRectangle,
this.Configuration,
(rows, vectorBuffer) =>
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
DenseMatrix<float> 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<TPixel> targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> 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<TPixel> targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
PixelOperations<TPixel>.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<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
}
}
}
}

7
src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs

@ -63,10 +63,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using (var processor = new Convolution2DProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
using var processor = new Convolution2DProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle);
processor.Apply(source);
}
}
}

133
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<float>[] 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<TPixel> cleanCopy = source.Clone();
// we need a clean copy for each pass to start from
using (ImageFrame<TPixel> cleanCopy = source.Clone())
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, kernels[0], true, this.Source, interest))
{
using (var processor = new ConvolutionProcessor<TPixel>(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<TPixel> pass = cleanCopy.Clone();
// Reset offset if necessary.
if (minX > 0)
using (var processor = new ConvolutionProcessor<TPixel>(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);
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="EdgeDetectorCompassProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalOperation : IRowIntervalOperation
{
private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> passPixels;
private readonly int minX;
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> 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++)
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
for (int y = rows.Min; y < rows.Max; y++)
{
using (ImageFrame<TPixel> 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<TPixel>(this.Configuration, kernels[i], true, this.Source, this.SourceRectangle))
{
processor.Apply(pass);
}
Buffer2D<TPixel> passPixels = pass.PixelBuffer;
Buffer2D<TPixel> 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);
}
}
}

7
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs

@ -44,10 +44,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using (var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle);
processor.Apply(source);
}
}
}

7
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs

@ -44,10 +44,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using (var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle);
processor.Apply(source);
}
}
}

68
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);
}
/// <summary>
/// A <see langword="struct"/> implementing the draw logic for <see cref="DrawImageProcessor{TPixelBg,TPixelFg}"/>.
/// </summary>
private readonly struct RowIntervalOperation : IRowIntervalOperation
{
private readonly ImageFrame<TPixelBg> sourceFrame;
private readonly Image<TPixelFg> targetImage;
private readonly PixelBlender<TPixelBg> 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<TPixelBg> sourceFrame,
Image<TPixelFg> targetImage,
PixelBlender<TPixelBg> 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;
}
/// <inheritdoc/>
[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<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixelFg> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity);
}
});
Span<TPixelBg> background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width);
Span<TPixelFg> foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width);
this.blender.Blend<TPixelFg>(this.configuration, background, background, foreground, this.opacity);
}
}
}
}
}

21
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
{
/// <summary>
/// An <see langword="interface"/> used by the row delegates for a given <see cref="PixelRowDelegateProcessor{TPixel,TDelegate}"/> instance
/// </summary>
public interface IPixelRowDelegate
{
/// <summary>
/// Applies the current pixel row delegate to a target row of preprocessed pixels.
/// </summary>
/// <param name="span">The target row of <see cref="Vector4"/> pixels to process.</param>
/// <param name="offset">The initial horizontal and vertical offset for the input pixels to process.</param>
void Invoke(Span<Vector4> span, Point offset);
}
}

205
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<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(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<Vector4> sourceRowBuffer = configuration.MemoryAllocator.Allocate<Vector4>(rowWidth))
using (IMemoryOwner<Vector4> targetRowBuffer = configuration.MemoryAllocator.Allocate<Vector4>(rowWidth))
using (IMemoryOwner<float> bins = configuration.MemoryAllocator.Allocate<float>(levels * 4))
{
Span<Vector4> sourceRowVector4Span = sourceRowBuffer.Memory.Span;
Span<Vector4> sourceRowAreaVector4Span = sourceRowVector4Span.Slice(startX, rectangleWidth);
this.SourceRectangle,
in operation);
Span<Vector4> targetRowVector4Span = targetRowBuffer.Memory.Span;
Span<Vector4> targetRowAreaVector4Span = targetRowVector4Span.Slice(startX, rectangleWidth);
ref float binsRef = ref bins.GetReference();
ref int intensityBinRef = ref Unsafe.As<float, int>(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<TPixel> sourceRowPixelSpan = source.GetPixelRowSpan(y);
Span<TPixel> sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(startX, rectangleWidth);
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
PixelOperations<TPixel>.Instance.ToVector4(configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span);
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="OilPaintingProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle bounds;
private readonly Buffer2D<TPixel> targetPixels;
private readonly ImageFrame<TPixel> source;
private readonly Configuration configuration;
private readonly int radius;
private readonly int levels;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
ImageFrame<TPixel> 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;
/// <inheritdoc/>
[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<Vector4> sourceRowBuffer = this.configuration.MemoryAllocator.Allocate<Vector4>(this.source.Width);
using IMemoryOwner<Vector4> targetRowBuffer = this.configuration.MemoryAllocator.Allocate<Vector4>(this.source.Width);
using IMemoryOwner<float> bins = this.configuration.MemoryAllocator.Allocate<float>(this.levels * 4);
Span<Vector4> sourceRowVector4Span = sourceRowBuffer.Memory.Span;
Span<Vector4> sourceRowAreaVector4Span = sourceRowVector4Span.Slice(this.bounds.X, this.bounds.Width);
Span<Vector4> targetRowVector4Span = targetRowBuffer.Memory.Span;
Span<Vector4> targetRowAreaVector4Span = targetRowVector4Span.Slice(this.bounds.X, this.bounds.Width);
ref float binsRef = ref bins.GetReference();
ref int intensityBinRef = ref Unsafe.As<float, int>(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<TPixel> sourceRowPixelSpan = this.source.GetPixelRowSpan(y);
Span<TPixel> sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width);
// Clear the current shared buffer before processing each target pixel
bins.Memory.Span.Clear();
PixelOperations<TPixel>.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<TPixel> 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<TPixel> 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<TPixel> 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<TPixel>.Instance.FromVector4Destructive(configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan);
targetRowVector4Span[x] = new Vector4(red, green, blue, alpha);
}
}
});
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
Span<TPixel> targetRowAreaPixelSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X, this.bounds.Width);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan);
}
}
}
}
}

30
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
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
=> new PixelRowDelegateProcessor<TPixel>(configuration, this, source, sourceRectangle);
{
return new PixelRowDelegateProcessor<TPixel, PixelRowDelegate>(
new PixelRowDelegate(this.PixelRowOperation),
configuration,
this.Modifiers,
source,
sourceRectangle);
}
/// <summary>
/// A <see langword="struct"/> implementing the row processing logic for <see cref="PixelRowDelegateProcessor"/>.
/// </summary>
public readonly struct PixelRowDelegate : IPixelRowDelegate
{
private readonly PixelRowOperation pixelRowOperation;
[MethodImpl(InliningOptions.ShortMethod)]
public PixelRowDelegate(PixelRowOperation pixelRowOperation)
{
this.pixelRowOperation = pixelRowOperation;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(Span<Vector4> span, Point offset) => this.pixelRowOperation(span);
}
}
}

70
src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs

@ -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
{
/// <summary>
/// The base class for all processors that accept a user defined row processing delegate.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class PixelRowDelegateProcessorBase<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The <see cref="PixelConversionModifiers"/> to apply during the pixel conversions.
/// </summary>
private readonly PixelConversionModifiers modifiers;
/// <summary>
/// Initializes a new instance of the <see cref="PixelRowDelegateProcessorBase{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="modifiers">The <see cref="PixelConversionModifiers"/> to apply during the pixel conversions.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
protected PixelRowDelegateProcessorBase(Configuration configuration, PixelConversionModifiers modifiers, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
=> this.modifiers = modifiers;
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int startX = interest.X;
Configuration configuration = this.Configuration;
PixelConversionModifiers modifiers = this.modifiers;
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
interest,
this.Configuration,
(rows, vectorBuffer) =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
Span<TPixel> rowSpan = source.GetPixelRowSpan(y).Slice(startX, length);
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan, rowSpan, modifiers);
}
});
}
/// <summary>
/// Applies the current pixel row delegate to a target row of preprocessed pixels.
/// </summary>
/// <param name="span">The target row of <see cref="Vector4"/> pixels to process.</param>
/// <param name="offset">The initial horizontal and vertical offset for the input pixels to process.</param>
protected abstract void ApplyPixelRowDelegate(Span<Vector4> span, Point offset);
}
}

104
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
{
/// <summary>
/// The base class for all processors that accept a user defined row processing delegate.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TDelegate">The row processor type.</typeparam>
internal sealed class PixelRowDelegateProcessor<TPixel, TDelegate> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
where TDelegate : struct, IPixelRowDelegate
{
private readonly TDelegate rowDelegate;
/// <summary>
/// The <see cref="PixelConversionModifiers"/> to apply during the pixel conversions.
/// </summary>
private readonly PixelConversionModifiers modifiers;
/// <summary>
/// Initializes a new instance of the <see cref="PixelRowDelegateProcessor{TPixel,TDelegate}"/> class.
/// </summary>
/// <param name="rowDelegate">The row processor to use to process each pixel row</param>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="modifiers">The <see cref="PixelConversionModifiers"/> to apply during the pixel conversions.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public PixelRowDelegateProcessor(
in TDelegate rowDelegate,
Configuration configuration,
PixelConversionModifiers modifiers,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.rowDelegate = rowDelegate;
this.modifiers = modifiers;
}
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
var operation = new RowIntervalOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
this.Configuration,
interest,
in operation);
}
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="PixelRowDelegateProcessor{TPixel,TDelegate}"/>.
/// </summary>
private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4>
{
private readonly int startX;
private readonly ImageFrame<TPixel> source;
private readonly Configuration configuration;
private readonly PixelConversionModifiers modifiers;
private readonly TDelegate rowProcessor;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
int startX,
ImageFrame<TPixel> source,
Configuration configuration,
PixelConversionModifiers modifiers,
in TDelegate rowProcessor)
{
this.startX = startX;
this.source = source;
this.configuration = configuration;
this.modifiers = modifiers;
this.rowProcessor = rowProcessor;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length);
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(this.configuration, span, rowSpan, this.modifiers);
}
}
}
}
}

39
src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel}.cs

@ -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
{
/// <summary>
/// Applies a user defined row processing delegate to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal sealed class PixelRowDelegateProcessor<TPixel> : PixelRowDelegateProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The user defined pixel row processing delegate.
/// </summary>
private readonly PixelRowOperation pixelRowOperation;
/// <summary>
/// Initializes a new instance of the <see cref="PixelRowDelegateProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="PixelRowDelegateProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public PixelRowDelegateProcessor(Configuration configuration, PixelRowDelegateProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, definition.Modifiers, source, sourceRectangle)
{
this.pixelRowOperation = definition.PixelRowOperation;
}
/// <inheritdoc/>
protected override void ApplyPixelRowDelegate(Span<Vector4> span, Point offset) => this.pixelRowOperation(span);
}
}

117
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
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public PixelateProcessor(Configuration configuration, PixelateProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.definition = definition;
}
=> this.definition = definition;
private int Size => this.definition.Size;
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
if (this.Size <= 0 || this.Size > source.Height || this.Size > source.Width)
{
throw new ArgumentOutOfRangeException(nameof(this.Size));
}
int startY = this.SourceRectangle.Y;
int endY = this.SourceRectangle.Bottom;
int startX = this.SourceRectangle.X;
int endX = this.SourceRectangle.Right;
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int size = this.Size;
int offset = this.Size / 2;
// Align start/end positions.
int minX = Math.Max(0, startX);
int maxX = Math.Min(source.Width, endX);
int minY = Math.Max(0, startY);
int maxY = Math.Min(source.Height, endY);
Guard.MustBeBetweenOrEqualTo(size, 0, interest.Width, nameof(size));
Guard.MustBeBetweenOrEqualTo(size, 0, interest.Height, nameof(size));
// 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<T> nor Span<T>
// implement IEnumerable<T>.
IEnumerable<int> range = EnumerableExtensions.SteppedRange(interest.Y, i => i < interest.Bottom, size);
Parallel.ForEach(
range,
this.Configuration.GetParallelOptions(),
new 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<TPixel> source;
[MethodImpl(InliningOptions.ShortMethod)]
public RowOperation(
Rectangle bounds,
int size,
ImageFrame<TPixel> source)
{
startX = 0;
this.minX = bounds.X;
this.maxX = bounds.Right;
this.maxXIndex = bounds.Right - 1;
this.maxY = bounds.Bottom;
this.maxYIndex = bounds.Bottom - 1;
this.size = size;
this.radius = size >> 1;
this.source = source;
}
if (minY > 0)
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
{
startY = 0;
}
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(Math.Min(y + this.radius, this.maxYIndex));
// Get the range on the y-plane to choose from.
IEnumerable<int> range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size);
for (int x = this.minX; x < this.maxX; x += this.size)
{
// Get the pixel color in the centre of the soon to be pixelated area.
TPixel pixel = rowSpan[Math.Min(x + this.radius, this.maxXIndex)];
Parallel.ForEach(
range,
this.Configuration.GetParallelOptions(),
y =>
// For each pixel in the pixelate size, set it to the centre color.
for (int oY = y; oY < y + this.size && oY < this.maxY; oY++)
{
int offsetY = y - startY;
int offsetPy = offset;
// Make sure that the offset is within the boundary of the image.
while (offsetY + offsetPy >= maxY)
for (int oX = x; oX < x + this.size && oX < this.maxX; oX++)
{
offsetPy--;
this.source[oX, oY] = pixel;
}
Span<TPixel> 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;
}
}
}
});
}
}
}
}
}
}

30
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
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
=> new PositionAwarePixelRowDelegateProcessor<TPixel>(configuration, this, source, sourceRectangle);
{
return new PixelRowDelegateProcessor<TPixel, PixelRowDelegate>(
new PixelRowDelegate(this.PixelRowOperation),
configuration,
this.Modifiers,
source,
sourceRectangle);
}
/// <summary>
/// A <see langword="struct"/> implementing the row processing logic for <see cref="PositionAwarePixelRowDelegateProcessor"/>.
/// </summary>
public readonly struct PixelRowDelegate : IPixelRowDelegate
{
private readonly PixelRowOperation<Point> pixelRowOperation;
[MethodImpl(InliningOptions.ShortMethod)]
public PixelRowDelegate(PixelRowOperation<Point> pixelRowOperation)
{
this.pixelRowOperation = pixelRowOperation;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(Span<Vector4> span, Point offset) => this.pixelRowOperation(span, offset);
}
}
}

36
src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor{TPixel}.cs

@ -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
{
/// <summary>
/// Applies a user defined, position aware, row processing delegate to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal sealed class PositionAwarePixelRowDelegateProcessor<TPixel> : PixelRowDelegateProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly PixelRowOperation<Point> pixelRowOperation;
/// <summary>
/// Initializes a new instance of the <see cref="PositionAwarePixelRowDelegateProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="PositionAwarePixelRowDelegateProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public PositionAwarePixelRowDelegateProcessor(Configuration configuration, PositionAwarePixelRowDelegateProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, definition.Modifiers, source, sourceRectangle)
{
this.pixelRowOperation = definition.PixelRowOperation;
}
/// <inheritdoc/>
protected override void ApplyPixelRowDelegate(Span<Vector4> span, Point offset) => this.pixelRowOperation(span, offset);
}
}

65
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<TPixel> 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<Vector4>(
interest,
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
this.Configuration,
(rows, vectorBuffer) =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
Span<TPixel> rowSpan = source.GetPixelRowSpan(y).Slice(startX, length);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, rowSpan, vectorSpan);
Vector4Utils.Transform(vectorSpan, ref matrix);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.Configuration, vectorSpan, rowSpan);
}
});
interest,
in operation);
}
/// <summary>
/// A <see langword="struct"/> implementing the convolution logic for <see cref="FilterProcessor{TPixel}"/>.
/// </summary>
private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4>
{
private readonly int startX;
private readonly ImageFrame<TPixel> source;
private readonly ColorMatrix matrix;
private readonly Configuration configuration;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
int startX,
ImageFrame<TPixel> source,
ColorMatrix matrix,
Configuration configuration)
{
this.startX = startX;
this.source = source;
this.matrix = matrix;
this.configuration = configuration;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, rowSpan, span);
Vector4Utils.Transform(span, ref Unsafe.AsRef(this.matrix));
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span, rowSpan);
}
}
}
}
}

302
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<TPixel> 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<TPixel> 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;
}
/// <inheritdoc/>
[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;
}
}
}
}
/// <summary>
/// Contains the results of the cumulative distribution function for all tiles.
/// </summary>
@ -431,7 +474,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
private readonly Buffer2D<int> 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<int>(tileCountX, tileCountY);
this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D<int>(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<TPixel> source, HistogramEqualizationProcessor<TPixel> 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<int> histogramBuffer = this.memoryAllocator.Allocate<int>(luminanceLevels))
{
Span<int> 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<TPixel> processor;
private readonly MemoryAllocator allocator;
private readonly Buffer2D<int> cdfMinBuffer2D;
private readonly Buffer2D<int> cdfLutBuffer2D;
private readonly List<(int y, int cdfY)> tileYStartPositions;
private readonly int tileWidth;
private readonly int tileHeight;
private readonly int luminanceLevels;
private readonly ImageFrame<TPixel> source;
private readonly int sourceWidth;
private readonly int sourceHeight;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
HistogramEqualizationProcessor<TPixel> processor,
MemoryAllocator allocator,
Buffer2D<int> cdfMinBuffer2D,
Buffer2D<int> cdfLutBuffer2D,
List<(int y, int cdfY)> tileYStartPositions,
int tileWidth,
int tileHeight,
int luminanceLevels,
ImageFrame<TPixel> 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;
}
/// <inheritdoc/>
[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<int> histogramBuffer = this.allocator.Allocate<int>(this.luminanceLevels);
Span<int> histogram = histogramBuffer.GetSpan();
ref int histogramBase = ref MemoryMarshal.GetReference(histogram);
for (int x = 0; x < this.sourceWidth; x += this.tileWidth)
{
histogram.Clear();
Span<int> 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++;
}
}
}
}
}
}
}

425
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<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(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<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="targetPixels">The target pixels.</param>
/// <param name="swInfos"><see cref="SlidingWindowInfos"/> about the sliding window dimensions.</param>
/// <param name="yStart">The y start position.</param>
/// <param name="yEnd">The y end position.</param>
/// <param name="useFastPath">if set to true the borders of the image will not be checked.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>Action Delegate.</returns>
private Action<int> ProcessSlidingWindow(
ImageFrame<TPixel> source,
MemoryAllocator memoryAllocator,
Buffer2D<TPixel> targetPixels,
SlidingWindowInfos swInfos,
int yStart,
int yEnd,
bool useFastPath,
Configuration configuration)
{
return x =>
{
using (IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
using (IMemoryOwner<int> histogramBufferCopy = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
using (IMemoryOwner<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
using (IMemoryOwner<Vector4> pixelRowBuffer = memoryAllocator.Allocate<Vector4>(swInfos.TileWidth, AllocationOptions.Clean))
{
Span<int> histogram = histogramBuffer.GetSpan();
ref int histogramBase = ref MemoryMarshal.GetReference(histogram);
Span<int> histogramCopy = histogramBufferCopy.GetSpan();
ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy);
ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan());
Span<Vector4> 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<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(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<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
/// <summary>
@ -371,6 +281,141 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
}
}
/// <summary>
/// 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.
/// </summary>
private readonly struct SlidingWindowOperation
{
private readonly Configuration configuration;
private readonly AdaptiveHistogramEqualizationSlidingWindowProcessor<TPixel> processor;
private readonly ImageFrame<TPixel> source;
private readonly MemoryAllocator memoryAllocator;
private readonly Buffer2D<TPixel> targetPixels;
private readonly SlidingWindowInfos swInfos;
private readonly int yStart;
private readonly int yEnd;
private readonly bool useFastPath;
/// <summary>
/// Initializes a new instance of the <see cref="SlidingWindowOperation"/> struct.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="processor">The histogram processor.</param>
/// <param name="source">The source image.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="targetPixels">The target pixels.</param>
/// <param name="swInfos"><see cref="SlidingWindowInfos"/> about the sliding window dimensions.</param>
/// <param name="yStart">The y start position.</param>
/// <param name="yEnd">The y end position.</param>
/// <param name="useFastPath">if set to true the borders of the image will not be checked.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public SlidingWindowOperation(
Configuration configuration,
AdaptiveHistogramEqualizationSlidingWindowProcessor<TPixel> processor,
ImageFrame<TPixel> source,
MemoryAllocator memoryAllocator,
Buffer2D<TPixel> 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<int> histogramBuffer = this.memoryAllocator.Allocate<int>(this.processor.LuminanceLevels, AllocationOptions.Clean))
using (IMemoryOwner<int> histogramBufferCopy = this.memoryAllocator.Allocate<int>(this.processor.LuminanceLevels, AllocationOptions.Clean))
using (IMemoryOwner<int> cdfBuffer = this.memoryAllocator.Allocate<int>(this.processor.LuminanceLevels, AllocationOptions.Clean))
using (IMemoryOwner<Vector4> pixelRowBuffer = this.memoryAllocator.Allocate<Vector4>(this.swInfos.TileWidth, AllocationOptions.Clean))
{
Span<int> histogram = histogramBuffer.GetSpan();
ref int histogramBase = ref MemoryMarshal.GetReference(histogram);
Span<int> histogramCopy = histogramBufferCopy.GetSpan();
ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy);
ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan());
Span<Vector4> 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; }
}
}
}

169
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<int> histogramBuffer = memoryAllocator.Allocate<int>(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<int> histogram = histogramBuffer.GetSpan();
if (this.ClipHistogramEnabled)
{
this.ClipHistogram(histogram, this.ClipLimit);
}
using IMemoryOwner<int> cdfBuffer = memoryAllocator.Allocate<int>(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);
}
/// <summary>
/// A <see langword="struct"/> implementing the grayscale levels logic for <see cref="GlobalHistogramEqualizationProcessor{TPixel}"/>.
/// </summary>
private readonly struct GrayscaleLevelsRowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle bounds;
private readonly IMemoryOwner<int> histogramBuffer;
private readonly ImageFrame<TPixel> source;
private readonly int luminanceLevels;
[MethodImpl(InliningOptions.ShortMethod)]
public GrayscaleLevelsRowIntervalOperation(
Rectangle bounds,
IMemoryOwner<int> histogramBuffer,
ImageFrame<TPixel> source,
int luminanceLevels)
{
this.bounds = bounds;
this.histogramBuffer = histogramBuffer;
this.source = source;
this.luminanceLevels = luminanceLevels;
}
using (IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
using (IMemoryOwner<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
/// <inheritdoc/>
[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<int> 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)++;
}
}
}
}
/// <summary>
/// A <see langword="struct"/> implementing the cdf application levels logic for <see cref="GlobalHistogramEqualizationProcessor{TPixel}"/>.
/// </summary>
private readonly struct CdfApplicationRowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle bounds;
private readonly IMemoryOwner<int> cdfBuffer;
private readonly ImageFrame<TPixel> source;
private readonly int luminanceLevels;
private readonly float numberOfPixelsMinusCdfMin;
[MethodImpl(InliningOptions.ShortMethod)]
public CdfApplicationRowIntervalOperation(
Rectangle bounds,
IMemoryOwner<int> cdfBuffer,
ImageFrame<TPixel> 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));
}
}
});
/// <inheritdoc/>
[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));
}
}
}
}
}

114
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
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public BackgroundColorProcessor(Configuration configuration, BackgroundColorProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.definition = definition;
}
=> this.definition = definition;
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
@ -39,65 +36,70 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
TPixel color = this.definition.Color.ToPixel<TPixel>();
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<TPixel> colors = memoryAllocator.Allocate<TPixel>(width))
using (IMemoryOwner<float> amount = memoryAllocator.Allocate<float>(width))
{
// Be careful! Do not capture colorSpan & amountSpan in the lambda below!
Span<TPixel> colorSpan = colors.GetSpan();
Span<float> amountSpan = amount.GetSpan();
using IMemoryOwner<TPixel> colors = memoryAllocator.Allocate<TPixel>(interest.Width);
using IMemoryOwner<float> amount = memoryAllocator.Allocate<float>(interest.Width);
colorSpan.Fill(color);
amountSpan.Fill(graphicsOptions.BlendPercentage);
colors.GetSpan().Fill(color);
amount.GetSpan().Fill(graphicsOptions.BlendPercentage);
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(graphicsOptions);
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(graphicsOptions);
ParallelHelper.IterateRows(
workingRect,
configuration,
rows =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> 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<TPixel> blender;
private readonly IMemoryOwner<float> amount;
private readonly IMemoryOwner<TPixel> colors;
private readonly ImageFrame<TPixel> source;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Configuration configuration,
Rectangle bounds,
PixelBlender<TPixel> blender,
IMemoryOwner<float> amount,
IMemoryOwner<TPixel> colors,
ImageFrame<TPixel> 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<TPixel> 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());
}
}
}
}

131
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
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> 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<TPixel>();
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<TPixel> rowColors = allocator.Allocate<TPixel>(interest.Width);
rowColors.GetSpan().Fill(glowColor);
if (minY > 0)
var operation = new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source);
ParallelRowIterator.IterateRows<RowIntervalOperation, float>(
configuration,
interest,
in operation);
}
private readonly struct RowIntervalOperation : IRowIntervalOperation<float>
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
private readonly PixelBlender<TPixel> blender;
private readonly Vector2 center;
private readonly float maxDistance;
private readonly float blendPercent;
private readonly IMemoryOwner<TPixel> colors;
private readonly ImageFrame<TPixel> source;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Configuration configuration,
Rectangle bounds,
IMemoryOwner<TPixel> colors,
PixelBlender<TPixel> blender,
Vector2 center,
float maxDistance,
float blendPercent,
ImageFrame<TPixel> 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<TPixel> rowColors = memoryAllocator.Allocate<TPixel>(width))
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<float> span)
{
rowColors.GetSpan().Fill(glowColor);
ParallelHelper.IterateRowsWithTempBuffer<float>(
workingRect,
configuration,
(rows, amounts) =>
{
Span<float> 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<TPixel> destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width);
this.blender.Blend(
configuration,
destination,
destination,
rowColors.GetSpan(),
amountsSpan);
}
});
Span<TPixel> 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<TPixel> destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
this.blender.Blend(
this.configuration,
destination,
destination,
colorSpan,
span);
}
}
}
}

140
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<TPixel>
{
private readonly PixelBlender<TPixel> blender;
private readonly VignetteProcessor definition;
/// <summary>
@ -40,80 +38,92 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> 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<TPixel>();
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<TPixel> rowColors = allocator.Allocate<TPixel>(interest.Width);
rowColors.GetSpan().Fill(vignetteColor);
var operation = new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source);
ParallelRowIterator.IterateRows<RowIntervalOperation, float>(
configuration,
interest,
in operation);
}
if (minY > 0)
private readonly struct RowIntervalOperation : IRowIntervalOperation<float>
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
private readonly PixelBlender<TPixel> blender;
private readonly Vector2 center;
private readonly float maxDistance;
private readonly float blendPercent;
private readonly IMemoryOwner<TPixel> colors;
private readonly ImageFrame<TPixel> source;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Configuration configuration,
Rectangle bounds,
IMemoryOwner<TPixel> colors,
PixelBlender<TPixel> blender,
Vector2 center,
float maxDistance,
float blendPercent,
ImageFrame<TPixel> 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<TPixel> rowColors = memoryAllocator.Allocate<TPixel>(width))
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<float> span)
{
rowColors.GetSpan().Fill(vignetteColor);
ParallelHelper.IterateRowsWithTempBuffer<float>(
workingRect,
configuration,
(rows, amounts) =>
{
Span<float> 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<TPixel> destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width);
this.blender.Blend(
configuration,
destination,
destination,
rowColors.GetSpan(),
amountsSpan);
}
});
Span<TPixel> 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<TPixel> destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
this.blender.Blend(
this.configuration,
destination,
destination,
colorSpan,
span);
}
}
}
}

56
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<TPixel> source)
{
Configuration configuration = this.Configuration;
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(configuration))
using (IQuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(source))
{
int paletteCount = quantized.Palette.Length - 1;
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(configuration);
using IQuantizedFrame<TPixel> 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<TPixel> row = source.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = quantized.GetPixelSpan();
var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized);
ParallelRowIterator.IterateRows(
configuration,
this.SourceRectangle,
in operation);
}
ReadOnlySpan<TPixel> paletteSpan = quantized.Palette.Span;
private readonly struct RowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source;
private readonly IQuantizedFrame<TPixel> quantized;
private readonly int maxPaletteIndex;
int yy = y * source.Width;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Rectangle bounds,
ImageFrame<TPixel> source,
IQuantizedFrame<TPixel> 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<byte> quantizedPixelSpan = this.quantized.GetPixelSpan();
ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette.Span;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> 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])];
}
}
}

176
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<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private Size targetSize;
private Matrix3x2 transformMatrix;
private readonly Size targetSize;
private readonly Matrix3x2 transformMatrix;
private readonly IResampler resampler;
/// <summary>
@ -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<TPixel> 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<RowIntervalOperation, Vector4>(
configuration,
targetBounds,
in operation);
}
/// <summary>
/// A <see langword="struct"/> implementing the nearest neighbor resampler logic for <see cref="AffineTransformProcessor{T}"/>.
/// </summary>
private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle bounds;
private readonly Matrix3x2 matrix;
private readonly int maxX;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public NearestNeighborRowIntervalOperation(
Rectangle bounds,
ref Matrix3x2 matrix,
int maxX,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination)
{
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
targetBounds,
configuration,
(rows, vectorBuffer) =>
this.bounds = bounds;
this.matrix = matrix;
this.maxX = maxX;
this.source = source;
this.destination = destination;
}
/// <inheritdoc/>
/// <param name="rows"></param>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> 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<Vector4> vectorSpan = vectorBuffer.Span;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(
configuration,
vectorSpan,
targetRowSpan);
}
});
destRow[x] = this.source[point.X, point.Y];
}
}
}
}
finally
}
/// <summary>
/// A <see langword="struct"/> implementing the transformation logic for <see cref="AffineTransformProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4>
{
private readonly Configuration configuration;
private readonly TransformKernelMap kernelMap;
private readonly Matrix3x2 matrix;
private readonly int maxX;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Configuration configuration,
TransformKernelMap kernelMap,
ref Matrix3x2 matrix,
int maxX,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination)
{
this.configuration = configuration;
this.kernelMap = kernelMap;
this.matrix = matrix;
this.maxX = maxX;
this.source = source;
this.destination = destination;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span)
{
kernel.Dispose();
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = this.destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(
this.configuration,
span,
targetRowSpan);
}
}
}
}

61
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<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private Rectangle cropRectangle;
private readonly Rectangle cropRectangle;
/// <summary>
/// Initializes a new instance of the <see cref="CropProcessor{TPixel}"/> 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<TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left);
Span<TPixel> targetRow = destination.GetPixelRowSpan(y - bounds.Top);
sourceRow.Slice(0, bounds.Width).CopyTo(targetRow);
}
});
in parallelSettings,
in operation);
}
/// <summary>
/// A <see langword="struct"/> implementing the processor logic for <see cref="CropProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
/// <summary>
/// Initializes a new instance of the <see cref="RowIntervalOperation"/> struct.
/// </summary>
/// <param name="bounds">The target processing bounds for the current instance.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current instance.</param>
/// <param name="destination">The destination <see cref="Image{TPixel}"/> for the current instance.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(Rectangle bounds, ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{
this.bounds = bounds;
this.source = source;
this.destination = destination;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left);
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top);
sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow);
}
}
}
}
}

55
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<TPixel> source, Configuration configuration)
{
int height = source.Height;
using IMemoryOwner<TPixel> tempBuffer = configuration.MemoryAllocator.Allocate<TPixel>(source.Width);
Span<TPixel> temp = tempBuffer.Memory.Span;
using (IMemoryOwner<TPixel> tempBuffer = configuration.MemoryAllocator.Allocate<TPixel>(source.Width))
for (int yTop = 0; yTop < height / 2; yTop++)
{
Span<TPixel> temp = tempBuffer.Memory.Span;
for (int yTop = 0; yTop < height / 2; yTop++)
{
int yBottom = height - yTop - 1;
Span<TPixel> topRow = source.GetPixelRowSpan(yBottom);
Span<TPixel> bottomRow = source.GetPixelRowSpan(yTop);
topRow.CopyTo(temp);
bottomRow.CopyTo(topRow);
temp.CopyTo(bottomRow);
}
int yBottom = height - yTop - 1;
Span<TPixel> topRow = source.GetPixelRowSpan(yBottom);
Span<TPixel> bottomRow = source.GetPixelRowSpan(yTop);
topRow.CopyTo(temp);
bottomRow.CopyTo(topRow);
temp.CopyTo(bottomRow);
}
}
@ -79,16 +76,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="configuration">The configuration.</param>
private void FlipY(ImageFrame<TPixel> 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<TPixel> source;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(ImageFrame<TPixel> 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();
}
}
}
}
}

176
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<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private Size targetSize;
private readonly Size targetSize;
private readonly IResampler resampler;
private Matrix4x4 transformMatrix;
private readonly Matrix4x4 transformMatrix;
/// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> 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<TPixel> 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<RowIntervalOperation, Vector4>(
configuration,
targetBounds,
in operation);
}
private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle bounds;
private readonly Matrix4x4 matrix;
private readonly int maxX;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public NearestNeighborRowIntervalOperation(
Rectangle bounds,
ref Matrix4x4 matrix,
int maxX,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination)
{
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
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<TPixel> 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<Vector4> vectorSpan = vectorBuffer.Span;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(
configuration,
vectorSpan,
targetRowSpan);
}
});
destRow[x] = this.source[px, py];
}
}
}
}
}
private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4>
{
private readonly Configuration configuration;
private readonly TransformKernelMap kernelMap;
private readonly Matrix4x4 matrix;
private readonly int maxX;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Configuration configuration,
TransformKernelMap kernelMap,
ref Matrix4x4 matrix,
int maxX,
ImageFrame<TPixel> source,
ImageFrame<TPixel> 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<Vector4> span)
{
kernel.Dispose();
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = this.destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(
this.configuration,
span,
targetRowSpan);
}
}
}
}

4
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);
}
}
}
}

94
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<TPixel> sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY));
Span<TPixel> 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<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Rectangle sourceBounds,
Rectangle destinationBounds,
float widthFactor,
float heightFactor,
ImageFrame<TPixel> source,
ImageFrame<TPixel> 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<TPixel> sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY));
Span<TPixel> 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)];
}
}
}
}
}
}

2
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".
/// </summary>
internal class ResizeWorker<TPixel> : IDisposable
internal sealed class ResizeWorker<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel>
{
private readonly Buffer2D<Vector4> transposedFirstPassBuffer;

224
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<TPixel> : AffineTransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly float degrees;
/// <summary>
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class.
/// </summary>
@ -26,11 +28,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public RotateProcessor(Configuration configuration, RotateProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, definition, source, sourceRectangle)
=> this.degrees = definition.Degrees;
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{
this.Degrees = definition.Degrees;
}
if (this.OptimizedApply(source, destination, this.Configuration))
{
return;
}
private float Degrees { get; }
base.OnFrameApply(source, destination);
}
/// <inheritdoc/>
protected override void AfterImageApply(Image<TPixel> 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);
}
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{
if (this.OptimizedApply(source, destination, this.Configuration))
{
return;
}
base.OnFrameApply(source, destination);
}
/// <summary>
/// Wraps a given angle in degrees so that it falls withing the 0-360 degree range
/// </summary>
@ -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
/// <param name="configuration">The configuration.</param>
private void Rotate180(ImageFrame<TPixel> source, ImageFrame<TPixel> 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<TPixel> sourceRow = source.GetPixelRowSpan(y);
Span<TPixel> targetRow = destination.GetPixelRowSpan(height - y - 1);
for (int x = 0; x < width; x++)
{
targetRow[width - x - 1] = sourceRow[x];
}
}
});
source.Bounds(),
in operation);
}
/// <summary>
@ -162,31 +146,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="configuration">The configuration.</param>
private void Rotate270(ImageFrame<TPixel> source, ImageFrame<TPixel> 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<TPixel> 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);
}
/// <summary>
@ -197,28 +161,132 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="configuration">The configuration.</param>
private void Rotate90(ImageFrame<TPixel> source, ImageFrame<TPixel> 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<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public Rotate180RowIntervalOperation(
int width,
int height,
ImageFrame<TPixel> source,
ImageFrame<TPixel> 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<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
Span<TPixel> 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<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public Rotate270RowIntervalOperation(
Rectangle bounds,
int width,
int height,
ImageFrame<TPixel> source,
ImageFrame<TPixel> 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<TPixel> 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<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public Rotate90RowIntervalOperation(
Rectangle bounds,
int width,
int height,
ImageFrame<TPixel> source,
ImageFrame<TPixel> 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<TPixel> 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<TPixel> 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];
}
});
}
}
}
}
}
}

1
tests/Directory.Build.targets

@ -26,6 +26,7 @@
<ItemGroup>
<!--Test Dependencies-->
<PackageReference Update="BenchmarkDotNet" Version="0.12.0" />
<PackageReference Update="BenchmarkDotNet.Diagnostics.Windows" Version="0.12.0" Condition="'$(OS)' == 'Windows_NT'" />
<PackageReference Update="Colourful" Version="2.0.3" />
<PackageReference Update="coverlet.collector" Version="1.2.0" PrivateAssets="All"/>
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.14.4" />

4
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<Rgba32>(400, 400))
{
image[200, 200] = Rgba32.White;
image[200, 200] = Color.White;
return image[200, 200];
}
}

22
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
}
}

1
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -19,6 +19,7 @@
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" />
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Condition="'$(OS)' == 'Windows_NT'" />
<PackageReference Include="Colourful" />
<PackageReference Include="Pfim" />
<PackageReference Include="System.Drawing.Common" />

1
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")]

2
tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
[Benchmark]
public Size DoDiffuse()
{
using (var image = new Image<Rgba32>(Configuration.Default, 800, 800, Rgba32.BlanchedAlmond))
using (var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond))
{
image.Mutate(x => x.Diffuse());

2
tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
[Benchmark]
public void Blur()
{
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Rgba32.White))
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.White))
{
image.Mutate(c => c.GaussianBlur());
}

2
tests/ImageSharp.Benchmarks/Samplers/Rotate.cs

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
[Benchmark]
public Size DoRotate()
{
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond))
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond))
{
image.Mutate(x => x.Rotate(37.5F));

2
tests/ImageSharp.Benchmarks/Samplers/Skew.cs

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
[Benchmark]
public Size DoSkew()
{
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond))
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond))
{
image.Mutate(x => x.Skew(20, 10));

138
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<ArgumentException>(() => Color.FromHex(string.Empty));
Assert.Throws<ArgumentException>(() => Color.ParseHex(string.Empty));
}
[Fact]
public void ThrowsOnInvalid()
{
Assert.Throws<ArgumentException>(() => Color.ParseHex("!"));
}
[Fact]
public void ThrowsOnNull()
{
Assert.Throws<ArgumentNullException>(() => Color.FromHex(null));
Assert.Throws<ArgumentNullException>(() => 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<ArgumentException>(() => Color.Parse(string.Empty));
}
[Fact]
public void ThrowsOnInvalid()
{
Assert.Throws<ArgumentException>(() => Color.Parse("!"));
}
[Fact]
public void ThrowsOnNull()
{
Assert.Throws<ArgumentNullException>(() => 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 _));
}
}
}

665
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
/// <summary>
/// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4.
/// </summary>
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
};
/// <summary>
/// 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 <see href="https://www.c82.net/werner"/>
/// </summary>
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<string, Color> ColorNames =
new Dictionary<string, Color>(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 }
};
}
}

4
tests/ImageSharp.Tests/Drawing/DrawImageTests.cs

@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
using (Image<Rgba32> background = provider.GetImage())
using (var overlay = new Image<Rgba32>(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<Rgba32> provider, int x, int y)
{
using (Image<Rgba32> background = provider.GetImage())
using (var overlay = new Image<Rgba32>(Configuration.Default, 10, 10, Rgba32.Black))
using (var overlay = new Image<Rgba32>(Configuration.Default, 10, 10, Color.Black))
{
ImageProcessingException ex = Assert.Throws<ImageProcessingException>(Test);

2
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

307
tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs → 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<T>(RowInterval rows, Span<T> 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
/// <summary>
/// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength
/// </summary>
public static TheoryData<int, int, int, int, int> IterateRows_OverMinimumPixelsLimit_Data =
new TheoryData<int, int, int, int, int>
{
{ 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<int, int, int, int, int, int> IterateRows_OverMinimumPixelsLimit_Data =
new TheoryData<int, int, int, int, int, int>
{
{ 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>();
int actualNumberOfSteps = 0;
ParallelHelper.IterateRowsWithTempBuffer(
rectangle,
parallelSettings,
(RowInterval rows, Memory<Vector4> buffer) =>
{
Assert.True(rows.Min >= minY);
Assert.True(rows.Max <= maxY);
bufferHashes.Add(buffer.GetHashCode());
void RowAction(RowInterval rows, Span<Vector4> 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<Vector4>(RowAction);
Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps);
ParallelRowIterator.IterateRows<TestRowIntervalOperation<Vector4>, 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<Vector4> buffer)
{
for (int y = rows.Min; y < rows.Max; y++)
{
actualData[y] = y;
}
}
var operation = new TestRowIntervalOperation<Vector4>(RowAction);
ParallelRowIterator.IterateRows<TestRowIntervalOperation<Vector4>, Vector4>(
rectangle,
parallelSettings,
(RowInterval rows, Memory<Vector4> buffer) =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
actualData[y] = y;
}
});
in parallelSettings,
in operation);
Assert.Equal(expectedData, actualData);
}
public static TheoryData<int, int, int, int, int, int, int> IterateRows_WithEffectiveMinimumPixelsLimit_Data =
new TheoryData<int, int, int, int, int, int, int>
{
{ 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<Vector4> 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<Vector4> 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<Vector4>(RowAction);
ParallelRowIterator.IterateRows<TestRowIntervalOperation<Vector4>, Vector4>(
rectangle,
in parallelSettings,
in operation);
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
}
public static readonly TheoryData<int, int, int, int, int, int, int> IterateRectangularBuffer_Data =
new TheoryData<int, int, int, int, int, int, int>
{
{ 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<ArgumentOutOfRangeException>(
() => 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<Rgba32> memory)
{
}
var operation = new TestRowIntervalOperation<Rgba32>(RowAction);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
() => ParallelHelper.IterateRowsWithTempBuffer<Rgba32>(rect, parallelSettings, (rows, memory) => { }));
() => ParallelRowIterator.IterateRows<TestRowIntervalOperation<Rgba32>, Rgba32>(rect, in parallelSettings, in operation));
Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message);
}
private readonly struct TestRowIntervalOperation : IRowIntervalOperation
{
private readonly Action<RowInterval> action;
public TestRowIntervalOperation(Action<RowInterval> action)
=> this.action = action;
public void Invoke(in RowInterval rows) => this.action(rows);
}
private readonly struct TestRowIntervalOperation<TBuffer> : IRowIntervalOperation<TBuffer>
where TBuffer : unmanaged
{
private readonly RowIntervalAction<TBuffer> action;
public TestRowIntervalOperation(RowIntervalAction<TBuffer> action)
=> this.action = action;
public void Invoke(in RowInterval rows, Span<TBuffer> span)
=> this.action(rows, span);
}
}
}

4
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]

8
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<Rgba32>)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<Rgba32>)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<Rgba32>)this.Image.Frames[1];
frame.ComparePixelBufferTo(Rgba32.HotPink);
frame.ComparePixelBufferTo(Color.HotPink);
}
[Fact]

22
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<Rgba32> img = useSpan
? Image.LoadPixelData<Rgba32>(data.AsSpan(), 2, 2)
: Image.LoadPixelData<Rgba32>(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<Rgba32>(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]);
}
}
}
}
}
}

2
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<Rgba32>(configuration, 11, 23, color))
{

42
tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs

@ -45,15 +45,15 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
public static TheoryData<Rgba32, Rgba32, float, PixelColorBlendingMode, Rgba32> ColorBlendingExpectedResults = new TheoryData<Rgba32, Rgba32, float, PixelColorBlendingMode, Rgba32>
{
{ 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<Rgba32, Rgba32, float, PixelAlphaCompositionMode, Rgba32> AlphaCompositionExpectedResults = new TheoryData<Rgba32, Rgba32, float, PixelAlphaCompositionMode, Rgba32>
{
{ 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]

16
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);

2
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));

2
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<GlowProcessor>();
Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer);

4
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()

2
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<TPixel>(TestImageProvider<TPixel> provider, TaperSide taperSide, TaperCorner taperCorner)
where TPixel : struct, IPixel<TPixel>
{

4
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<Rgba32>(config, 1, 1, Rgba32.Black))
using (var image = new Image<Rgba32>(config, 1, 1, Color.Black))
using (IQuantizedFrame<Rgba32> result = quantizer.CreateFrameQuantizer<Rgba32>(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]);
}
}

6
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<TPixel>().ToVector4(); // use real color so we can see how it translates in the test pattern
var green = Color.Green.ToPixel<TPixel>().ToVector4(); // use real color so we can see how it translates in the test pattern
var blue = Color.Blue.ToPixel<TPixel>().ToVector4(); // use real color so we can see how it translates in the test pattern
var c = default(TPixel);

53
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<Vector4>(
sourceRectangle,
var operation = new RowOperation(configuration, sourceRectangle, source);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
configuration,
(rows, temp) =>
sourceRectangle,
in operation);
}
private readonly struct RowOperation : IRowIntervalOperation<Vector4>
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source;
public RowOperation(Configuration configuration, Rectangle bounds, ImageFrame<TPixel> source)
{
this.configuration = configuration;
this.bounds = bounds;
this.source = source;
}
public void Invoke(in RowInterval rows, Span<Vector4> span)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left, this.bounds.Width);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale);
for (int i = 0; i < span.Length; i++)
{
Span<Vector4> tempSpan = temp.Span;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> rowSpan = source.GetPixelRowSpan(y).Slice(sourceRectangle.Left, sourceRectangle.Width);
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(configuration, tempSpan, rowSpan, PixelConversionModifiers.Scale);
}
});
ref Vector4 v = ref span[i];
v.W = 1F;
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale);
}
}
}
}
}

Loading…
Cancel
Save