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. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -6,10 +6,10 @@ using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Advanced.ParallelUtils namespace SixLabors.ImageSharp.Advanced
{ {
/// <summary> /// <summary>
/// Defines execution settings for methods in <see cref="ParallelHelper"/>. /// Defines execution settings for methods in <see cref="ParallelRowIterator"/>.
/// </summary> /// </summary>
public readonly struct ParallelExecutionSettings public readonly struct ParallelExecutionSettings
{ {
@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
} }
/// <summary> /// <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> /// </summary>
/// <param name="configuration">The <see cref="Configuration"/>.</param> /// <param name="configuration">The <see cref="Configuration"/>.</param>
/// <returns>The <see cref="ParallelExecutionSettings"/>.</returns> /// <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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
/// <content> /// <content>
@ -8,6 +11,8 @@ namespace SixLabors.ImageSharp
/// </content> /// </content>
public readonly partial struct Color public readonly partial struct Color
{ {
private static readonly Lazy<Dictionary<string, Color>> NamedColorsLookupLazy = new Lazy<Dictionary<string, Color>>(CreateNamedColorsLookup, true);
/// <summary> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0F8FF. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0F8FF.
/// </summary> /// </summary>
@ -111,7 +116,7 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00FFFF. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00FFFF.
/// </summary> /// </summary>
public static readonly Color Cyan = FromRgba(0, 255, 255, 255); public static readonly Color Cyan = Aqua;
/// <summary> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00008B. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00008B.
@ -138,6 +143,11 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
public static readonly Color DarkGreen = FromRgba(0, 100, 0, 255); 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> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #BDB76B. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #BDB76B.
/// </summary> /// </summary>
@ -188,6 +198,11 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
public static readonly Color DarkSlateGray = FromRgba(47, 79, 79, 255); 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> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00CED1. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00CED1.
/// </summary> /// </summary>
@ -213,6 +228,11 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
public static readonly Color DimGray = FromRgba(105, 105, 105, 255); 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> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #1E90FF. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #1E90FF.
/// </summary> /// </summary>
@ -273,6 +293,11 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
public static readonly Color GreenYellow = FromRgba(173, 255, 47, 255); 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> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0FFF0. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0FFF0.
/// </summary> /// </summary>
@ -353,6 +378,11 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
public static readonly Color LightGreen = FromRgba(144, 238, 144, 255); 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> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFB6C1. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFB6C1.
/// </summary> /// </summary>
@ -378,6 +408,11 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
public static readonly Color LightSlateGray = FromRgba(119, 136, 153, 255); 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> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #B0C4DE. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #B0C4DE.
/// </summary> /// </summary>
@ -406,7 +441,7 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF00FF. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF00FF.
/// </summary> /// </summary>
public static readonly Color Magenta = FromRgba(255, 0, 255, 255); public static readonly Color Magenta = Fuchsia;
/// <summary> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #800000. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #800000.
@ -643,6 +678,11 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
public static readonly Color SlateGray = FromRgba(112, 128, 144, 255); 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> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFAFA. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFAFA.
/// </summary> /// </summary>
@ -717,5 +757,161 @@ namespace SixLabors.ImageSharp
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #9ACD32. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #9ACD32.
/// </summary> /// </summary>
public static readonly Color YellowGreen = FromRgba(154, 205, 50, 255); 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[] private static Color[] CreateWernerPalette() => new[]
{ {
FromHex("#f1e9cd"), ParseHex("#f1e9cd"),
FromHex("#f2e7cf"), ParseHex("#f2e7cf"),
FromHex("#ece6d0"), ParseHex("#ece6d0"),
FromHex("#f2eacc"), ParseHex("#f2eacc"),
FromHex("#f3e9ca"), ParseHex("#f3e9ca"),
FromHex("#f2ebcd"), ParseHex("#f2ebcd"),
FromHex("#e6e1c9"), ParseHex("#e6e1c9"),
FromHex("#e2ddc6"), ParseHex("#e2ddc6"),
FromHex("#cbc8b7"), ParseHex("#cbc8b7"),
FromHex("#bfbbb0"), ParseHex("#bfbbb0"),
FromHex("#bebeb3"), ParseHex("#bebeb3"),
FromHex("#b7b5ac"), ParseHex("#b7b5ac"),
FromHex("#bab191"), ParseHex("#bab191"),
FromHex("#9c9d9a"), ParseHex("#9c9d9a"),
FromHex("#8a8d84"), ParseHex("#8a8d84"),
FromHex("#5b5c61"), ParseHex("#5b5c61"),
FromHex("#555152"), ParseHex("#555152"),
FromHex("#413f44"), ParseHex("#413f44"),
FromHex("#454445"), ParseHex("#454445"),
FromHex("#423937"), ParseHex("#423937"),
FromHex("#433635"), ParseHex("#433635"),
FromHex("#252024"), ParseHex("#252024"),
FromHex("#241f20"), ParseHex("#241f20"),
FromHex("#281f3f"), ParseHex("#281f3f"),
FromHex("#1c1949"), ParseHex("#1c1949"),
FromHex("#4f638d"), ParseHex("#4f638d"),
FromHex("#383867"), ParseHex("#383867"),
FromHex("#5c6b8f"), ParseHex("#5c6b8f"),
FromHex("#657abb"), ParseHex("#657abb"),
FromHex("#6f88af"), ParseHex("#6f88af"),
FromHex("#7994b5"), ParseHex("#7994b5"),
FromHex("#6fb5a8"), ParseHex("#6fb5a8"),
FromHex("#719ba2"), ParseHex("#719ba2"),
FromHex("#8aa1a6"), ParseHex("#8aa1a6"),
FromHex("#d0d5d3"), ParseHex("#d0d5d3"),
FromHex("#8590ae"), ParseHex("#8590ae"),
FromHex("#3a2f52"), ParseHex("#3a2f52"),
FromHex("#39334a"), ParseHex("#39334a"),
FromHex("#6c6d94"), ParseHex("#6c6d94"),
FromHex("#584c77"), ParseHex("#584c77"),
FromHex("#533552"), ParseHex("#533552"),
FromHex("#463759"), ParseHex("#463759"),
FromHex("#bfbac0"), ParseHex("#bfbac0"),
FromHex("#77747f"), ParseHex("#77747f"),
FromHex("#4a475c"), ParseHex("#4a475c"),
FromHex("#b8bfaf"), ParseHex("#b8bfaf"),
FromHex("#b2b599"), ParseHex("#b2b599"),
FromHex("#979c84"), ParseHex("#979c84"),
FromHex("#5d6161"), ParseHex("#5d6161"),
FromHex("#61ac86"), ParseHex("#61ac86"),
FromHex("#a4b6a7"), ParseHex("#a4b6a7"),
FromHex("#adba98"), ParseHex("#adba98"),
FromHex("#93b778"), ParseHex("#93b778"),
FromHex("#7d8c55"), ParseHex("#7d8c55"),
FromHex("#33431e"), ParseHex("#33431e"),
FromHex("#7c8635"), ParseHex("#7c8635"),
FromHex("#8e9849"), ParseHex("#8e9849"),
FromHex("#c2c190"), ParseHex("#c2c190"),
FromHex("#67765b"), ParseHex("#67765b"),
FromHex("#ab924b"), ParseHex("#ab924b"),
FromHex("#c8c76f"), ParseHex("#c8c76f"),
FromHex("#ccc050"), ParseHex("#ccc050"),
FromHex("#ebdd99"), ParseHex("#ebdd99"),
FromHex("#ab9649"), ParseHex("#ab9649"),
FromHex("#dbc364"), ParseHex("#dbc364"),
FromHex("#e6d058"), ParseHex("#e6d058"),
FromHex("#ead665"), ParseHex("#ead665"),
FromHex("#d09b2c"), ParseHex("#d09b2c"),
FromHex("#a36629"), ParseHex("#a36629"),
FromHex("#a77d35"), ParseHex("#a77d35"),
FromHex("#f0d696"), ParseHex("#f0d696"),
FromHex("#d7c485"), ParseHex("#d7c485"),
FromHex("#f1d28c"), ParseHex("#f1d28c"),
FromHex("#efcc83"), ParseHex("#efcc83"),
FromHex("#f3daa7"), ParseHex("#f3daa7"),
FromHex("#dfa837"), ParseHex("#dfa837"),
FromHex("#ebbc71"), ParseHex("#ebbc71"),
FromHex("#d17c3f"), ParseHex("#d17c3f"),
FromHex("#92462f"), ParseHex("#92462f"),
FromHex("#be7249"), ParseHex("#be7249"),
FromHex("#bb603c"), ParseHex("#bb603c"),
FromHex("#c76b4a"), ParseHex("#c76b4a"),
FromHex("#a75536"), ParseHex("#a75536"),
FromHex("#b63e36"), ParseHex("#b63e36"),
FromHex("#b5493a"), ParseHex("#b5493a"),
FromHex("#cd6d57"), ParseHex("#cd6d57"),
FromHex("#711518"), ParseHex("#711518"),
FromHex("#e9c49d"), ParseHex("#e9c49d"),
FromHex("#eedac3"), ParseHex("#eedac3"),
FromHex("#eecfbf"), ParseHex("#eecfbf"),
FromHex("#ce536b"), ParseHex("#ce536b"),
FromHex("#b74a70"), ParseHex("#b74a70"),
FromHex("#b7757c"), ParseHex("#b7757c"),
FromHex("#612741"), ParseHex("#612741"),
FromHex("#7a4848"), ParseHex("#7a4848"),
FromHex("#3f3033"), ParseHex("#3f3033"),
FromHex("#8d746f"), ParseHex("#8d746f"),
FromHex("#4d3635"), ParseHex("#4d3635"),
FromHex("#6e3b31"), ParseHex("#6e3b31"),
FromHex("#864735"), ParseHex("#864735"),
FromHex("#553d3a"), ParseHex("#553d3a"),
FromHex("#613936"), ParseHex("#613936"),
FromHex("#7a4b3a"), ParseHex("#7a4b3a"),
FromHex("#946943"), ParseHex("#946943"),
FromHex("#c39e6d"), ParseHex("#c39e6d"),
FromHex("#513e32"), ParseHex("#513e32"),
FromHex("#8b7859"), ParseHex("#8b7859"),
FromHex("#9b856b"), ParseHex("#9b856b"),
FromHex("#766051"), ParseHex("#766051"),
FromHex("#453b32") 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); public static Color FromRgb(byte r, byte g, byte b) => new Color(r, g, b);
/// <summary> /// <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> /// </summary>
/// <param name="hex"> /// <param name="hex">
/// The hexadecimal representation of the combined color components arranged /// The hexadecimal representation of the combined color components arranged
/// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
/// </param> /// </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)] [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); 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> /// <summary>
/// Alters the alpha channel of the color, returning a new instance. /// Alters the alpha channel of the color, returning a new instance.
/// </summary> /// </summary>
@ -117,7 +198,7 @@ namespace SixLabors.ImageSharp
/// <returns>The color having it's alpha channel altered.</returns> /// <returns>The color having it's alpha channel altered.</returns>
public Color WithAlpha(float alpha) public Color WithAlpha(float alpha)
{ {
Vector4 v = (Vector4)this; var v = (Vector4)this;
v.W = alpha; v.W = alpha;
return new Color(v); 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace SixLabors.ImageSharp.Common namespace SixLabors.ImageSharp
{ {
/// <summary> /// <summary>
/// Encapsulates a series of time saving extension methods to the <see cref="T:System.Collections.IEnumerable"/> interface. /// 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> /// <summary>
/// Generates a sequence of integral numbers within a specified range. /// Generates a sequence of integral numbers within a specified range.
/// </summary> /// </summary>
/// <param name="fromInclusive"> /// <param name="fromInclusive">The start index, inclusive.</param>
/// The start index, inclusive.
/// </param>
/// <param name="toDelegate"> /// <param name="toDelegate">
/// A method that has one parameter and returns a <see cref="bool"/> calculating the end index. /// A method that has one parameter and returns a <see cref="bool"/> calculating the end index.
/// </param> /// </param>
/// <param name="step"> /// <param name="step">The incremental step.</param>
/// The incremental step.
/// </param>
/// <returns> /// <returns>
/// The <see cref="IEnumerable{Int32}"/> that contains a range of sequential integral numbers. /// The <see cref="IEnumerable{Int32}"/> that contains a range of sequential integral numbers.
/// </returns> /// </returns>
@ -56,4 +52,4 @@ namespace SixLabors.ImageSharp.Common
} }
} }
} }
} }

51
src/ImageSharp/ImageFrame{TPixel}.cs

@ -4,9 +4,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; 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 target = new ImageFrame<TPixel2>(configuration, this.Width, this.Height, this.Metadata.DeepClone());
var operation = new RowIntervalOperation<TPixel2>(this, target, configuration);
ParallelHelper.IterateRows( ParallelRowIterator.IterateRows(
this.Bounds(),
configuration, configuration,
rows => this.Bounds(),
{ in operation);
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);
}
});
return target; return target;
} }
@ -295,5 +286,39 @@ namespace SixLabors.ImageSharp
group.Fill(value); 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); public static bool operator !=(Rgba32 left, Rgba32 right) => !left.Equals(right);
/// <summary> /// <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> /// </summary>
/// <param name="hex"> /// <param name="hex">
/// The hexadecimal representation of the combined color components arranged /// The hexadecimal representation of the combined color components arranged
@ -239,19 +240,50 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <returns> /// <returns>
/// The <see cref="Rgba32"/>. /// The <see cref="Rgba32"/>.
/// </returns> /// </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); hex = ToRgbaHex(hex);
if (hex is null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint packedValue)) 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); packedValue = BinaryPrimitives.ReverseEndianness(packedValue);
return Unsafe.As<uint, Rgba32>(ref packedValue); result = Unsafe.As<uint, Rgba32>(ref packedValue);
return true;
} }
/// <inheritdoc /> /// <inheritdoc />

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

@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <returns> /// <returns>
/// The <see cref="RgbaVector"/>. /// The <see cref="RgbaVector"/>.
/// </returns> /// </returns>
public static RgbaVector FromHex(string hex) => Color.FromHex(hex).ToPixel<RgbaVector>(); public static RgbaVector FromHex(string hex) => Color.ParseHex(hex).ToPixel<RgbaVector>();
/// <inheritdoc /> /// <inheritdoc />
public PixelOperations<RgbaVector> CreatePixelOperations() => new PixelOperations(); 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. // Licensed under the Apache License, Version 2.0.
using System; 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.Width.Equals(other.Width) &&
this.Height.Equals(other.Height); 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization namespace SixLabors.ImageSharp.Processing.Processors.Binarization
@ -42,36 +42,66 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); 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); bool isAlphaOnly = typeof(TPixel) == typeof(A8);
var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); var operation = new RowIntervalOperation(interest, source, upper, lower, threshold, isAlphaOnly);
ParallelRowIterator.IterateRows(
ParallelHelper.IterateRows(
workingRect,
configuration, configuration,
rows => interest,
{ in operation);
Rgba32 rgba = default; }
for (int y = rows.Min; y < rows.Max; y++)
{ /// <summary>
Span<TPixel> row = source.GetPixelRowSpan(y); /// 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++) [MethodImpl(InliningOptions.ShortMethod)]
{ public RowIntervalOperation(
ref TPixel color = ref row[x]; Rectangle bounds,
color.ToRgba32(ref rgba); 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 // Convert to grayscale using ITU-R Recommendation BT.709 if required
byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
color = luminance >= threshold ? upper : lower; 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.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters;
@ -269,17 +268,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
// Preliminary gamma highlight pass // 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 // 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)) 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);
// Apply the inverse gamma exposure pass, and write the final pixel data // Perform the 1D convolutions on all the kernel components and accumulate the results
this.ApplyInverseGammaExposure(source.PixelBuffer, processingBuffer, this.SourceRectangle, this.Configuration); 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> /// <summary>
@ -295,216 +303,223 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Configuration configuration, Configuration configuration,
Buffer2D<Vector4> processingBuffer) 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 // Compute the resulting complex buffer for the current component
ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); Complex64[] kernel = Unsafe.Add(ref baseRef, i);
ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); Vector4 parameters = Unsafe.Add(ref paramsRef, i);
for (int i = 0; i < this.kernels.Length; i++)
{ // Compute the vertical 1D convolution
// Compute the resulting complex buffer for the current component var verticalOperation = new ApplyVerticalConvolutionRowIntervalOperation(sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel);
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); ParallelRowIterator.IterateRows(
Complex64[] kernel = Unsafe.Add(ref baseRef, i); configuration,
Vector4 parameters = Unsafe.Add(ref paramsRef, i); sourceRectangle,
in verticalOperation);
// Compute the two 1D convolutions and accumulate the partial results on the target buffer
this.ApplyConvolution(firstPassBuffer, source.PixelBuffer, interest, kernel, configuration); // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer
this.ApplyConvolution(processingBuffer, firstPassBuffer, interest, kernel, configuration, parameters.Z, parameters.W); var horizontalOperation = new ApplyHorizontalConvolutionRowIntervalOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W);
} ParallelRowIterator.IterateRows(
configuration,
sourceRectangle,
in horizontalOperation);
} }
} }
/// <summary> /// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}"/> at the specified location /// A <see langword="struct"/> implementing the vertical convolution logic for <see cref="BokehBlurProcessor{T}"/>.
/// and with the specified size.
/// </summary> /// </summary>
/// <param name="targetValues">The target <see cref="ComplexVector4"/> values to use to store the results.</param> private readonly struct ApplyVerticalConvolutionRowIntervalOperation : IRowIntervalOperation
/// <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)
{ {
int startY = sourceRectangle.Y; private readonly Rectangle bounds;
int endY = sourceRectangle.Bottom; private readonly Buffer2D<ComplexVector4> targetValues;
int startX = sourceRectangle.X; private readonly Buffer2D<TPixel> sourcePixels;
int endX = sourceRectangle.Right; private readonly Complex64[] kernel;
int maxY = endY - 1; private readonly int maxY;
int maxX = endX - 1; private readonly int maxX;
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); [MethodImpl(InliningOptions.ShortMethod)]
int width = workingRectangle.Width; public ApplyVerticalConvolutionRowIntervalOperation(
Rectangle bounds,
ParallelHelper.IterateRows( Buffer2D<ComplexVector4> targetValues,
workingRectangle, Buffer2D<TPixel> sourcePixels,
configuration, Complex64[] kernel)
rows => {
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 = this.targetValues.GetRowSpanUnchecked(y).Slice(this.bounds.X);
{
Span<ComplexVector4> targetRowSpan = targetValues.GetRowSpanUnchecked(y).Slice(startX);
for (int x = 0; x < width; x++) for (int x = 0; x < this.bounds.Width; x++)
{ {
Buffer2DUtils.Convolve4(kernel, sourcePixels, targetRowSpan, y, x, startY, maxY, startX, maxX); Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX);
}
} }
}); }
}
} }
/// <summary> /// <summary>
/// Applies the process to the specified portion of the specified <see cref="Buffer2D{T}"/> buffer at the specified location /// A <see langword="struct"/> implementing the horizontal convolution logic for <see cref="BokehBlurProcessor{T}"/>.
/// and with the specified size.
/// </summary> /// </summary>
/// <param name="targetValues">The target <see cref="Vector4"/> values to use to store the results.</param> private readonly struct ApplyHorizontalConvolutionRowIntervalOperation : IRowIntervalOperation
/// <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)
{ {
int startY = sourceRectangle.Y; private readonly Rectangle bounds;
int endY = sourceRectangle.Bottom; private readonly Buffer2D<Vector4> targetValues;
int startX = sourceRectangle.X; private readonly Buffer2D<ComplexVector4> sourceValues;
int endX = sourceRectangle.Right; private readonly Complex64[] kernel;
int maxY = endY - 1; private readonly float z;
int maxX = endX - 1; private readonly float w;
private readonly int maxY;
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); private readonly int maxX;
int width = workingRectangle.Width;
[MethodImpl(InliningOptions.ShortMethod)]
ParallelHelper.IterateRows( public ApplyHorizontalConvolutionRowIntervalOperation(
workingRectangle, Rectangle bounds,
configuration, Buffer2D<Vector4> targetValues,
rows => 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 = this.targetValues.GetRowSpanUnchecked(y).Slice(this.bounds.X);
{
Span<Vector4> targetRowSpan = targetValues.GetRowSpanUnchecked(y).Slice(startX);
for (int x = 0; x < width; x++) for (int x = 0; x < this.bounds.Width; x++)
{ {
Buffer2DUtils.Convolve4AndAccumulatePartials(kernel, sourceValues, targetRowSpan, y, x, startY, maxY, startX, maxX, z, w); Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w);
}
} }
}); }
}
} }
/// <summary> /// <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> /// </summary>
/// <param name="targetPixels">The target pixel buffer to adjust.</param> private readonly struct ApplyGammaExposureRowIntervalOperation : IRowIntervalOperation<Vector4>
/// <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)
{ {
int startY = sourceRectangle.Y; private readonly Rectangle bounds;
int endY = sourceRectangle.Bottom; private readonly Buffer2D<TPixel> targetPixels;
int startX = sourceRectangle.X; private readonly Configuration configuration;
int endX = sourceRectangle.Right; private readonly float gamma;
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); [MethodImpl(InliningOptions.ShortMethod)]
int width = workingRectangle.Width; public ApplyGammaExposureRowIntervalOperation(
float exp = this.gamma; Rectangle bounds,
Buffer2D<TPixel> targetPixels,
ParallelHelper.IterateRowsWithTempBuffer<Vector4>( Configuration configuration,
workingRectangle, float gamma)
configuration, {
(rows, vectorBuffer) => 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; ref Vector4 v = ref Unsafe.Add(ref baseRef, x);
int length = vectorSpan.Length; v.X = MathF.Pow(v.X, this.gamma);
v.Y = MathF.Pow(v.Y, this.gamma);
for (int y = rows.Min; y < rows.Max; y++) v.Z = MathF.Pow(v.Z, this.gamma);
{ }
Span<TPixel> targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX);
PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
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);
}
});
} }
/// <summary> /// <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> /// </summary>
/// <param name="targetPixels">The target pixels to apply the process to.</param> private readonly struct ApplyInverseGammaExposureRowIntervalOperation : IRowIntervalOperation
/// <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)
{ {
int startY = sourceRectangle.Y; private readonly Rectangle bounds;
int endY = sourceRectangle.Bottom; private readonly Buffer2D<TPixel> targetPixels;
int startX = sourceRectangle.X; private readonly Buffer2D<Vector4> sourceValues;
int endX = sourceRectangle.Right; private readonly Configuration configuration;
private readonly float inverseGamma;
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
int width = workingRectangle.Width; [MethodImpl(InliningOptions.ShortMethod)]
float expGamma = 1 / this.gamma; public ApplyInverseGammaExposureRowIntervalOperation(
Rectangle bounds,
ParallelHelper.IterateRows( Buffer2D<TPixel> targetPixels,
workingRectangle, Buffer2D<Vector4> sourceValues,
configuration, Configuration configuration,
rows => 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; ref Vector4 v = ref Unsafe.Add(ref sourceRef, x);
var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); var clamp = Vector4.Clamp(v, low, high);
v.X = MathF.Pow(clamp.X, this.inverseGamma);
for (int y = rows.Min; y < rows.Max; y++) v.Y = MathF.Pow(clamp.Y, this.inverseGamma);
{ v.Z = MathF.Pow(clamp.Z, this.inverseGamma);
Span<TPixel> targetPixelSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX); }
Span<Vector4> sourceRowSpan = sourceValues.GetRowSpanUnchecked(y).Slice(startX);
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply);
}
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);
}
});
} }
} }
} }

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

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

164
src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs

@ -3,9 +3,9 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -60,79 +60,105 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
DenseMatrix<float> matrixY = this.KernelY; using Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height);
DenseMatrix<float> matrixX = this.KernelX;
bool preserveAlpha = this.PreserveAlpha; source.CopyTo(targetPixels);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int startY = interest.Y; var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha);
int endY = interest.Bottom;
int startX = interest.X; ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
int endX = interest.Right; this.Configuration,
int maxY = endY - 1; interest,
int maxX = endX - 1; in operation);
using (Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height)) 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); /// <inheritdoc/>
int width = workingRectangle.Width; [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span)
{
ref Vector4 spanRef = ref MemoryMarshal.GetReference(span);
ParallelHelper.IterateRowsWithTempBuffer<Vector4>( for (int y = rows.Min; y < rows.Max; y++)
workingRectangle, {
this.Configuration, Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X);
(rows, vectorBuffer) => 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; DenseMatrixUtils.Convolve2D4(
int length = vectorSpan.Length; in this.kernelY,
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); in this.kernelX,
this.sourcePixels,
for (int y = rows.Min; y < rows.Max; y++) ref spanRef,
{ y,
Span<TPixel> targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX); x,
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan); this.bounds.Y,
this.maxY,
if (preserveAlpha) this.bounds.X,
{ this.maxX);
for (int x = 0; x < width; x++) }
{ }
DenseMatrixUtils.Convolve2D3(
in matrixY, PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan);
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);
} }
} }
} }

175
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

@ -3,9 +3,9 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -59,95 +59,104 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using (Buffer2D<TPixel> firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size())) using Buffer2D<TPixel> firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size());
{
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); 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); // 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> /// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}"/> at the specified location /// A <see langword="struct"/> implementing the convolution logic for <see cref="Convolution2PassProcessor{T}"/>.
/// and with the specified size.
/// </summary> /// </summary>
/// <param name="targetPixels">The target pixels to apply the process to.</param> private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4>
/// <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)
{ {
DenseMatrix<float> matrix = kernel; private readonly Rectangle bounds;
bool preserveAlpha = this.PreserveAlpha; private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> sourcePixels;
int startY = sourceRectangle.Y; private readonly DenseMatrix<float> kernel;
int endY = sourceRectangle.Bottom; private readonly Configuration configuration;
int startX = sourceRectangle.X; private readonly bool preserveAlpha;
int endX = sourceRectangle.Right;
int maxY = endY - 1; [MethodImpl(InliningOptions.ShortMethod)]
int maxX = endX - 1; public RowIntervalOperation(
Rectangle bounds,
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); Buffer2D<TPixel> targetPixels,
int width = workingRectangle.Width; Buffer2D<TPixel> sourcePixels,
DenseMatrix<float> kernel,
ParallelHelper.IterateRowsWithTempBuffer<Vector4>( Configuration configuration,
workingRectangle, bool preserveAlpha)
configuration, {
(rows, vectorBuffer) => this.bounds = bounds;
{ this.targetPixels = targetPixels;
Span<Vector4> vectorSpan = vectorBuffer.Span; this.sourcePixels = sourcePixels;
int length = vectorSpan.Length; this.kernel = kernel;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); 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); DenseMatrixUtils.Convolve3(
PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); in this.kernel,
this.sourcePixels,
if (preserveAlpha) ref spanRef,
{ y,
for (int x = 0; x < width; x++) x,
{ this.bounds.Y,
DenseMatrixUtils.Convolve3( maxY,
in matrix, this.bounds.X,
sourcePixels, maxX);
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);
} }
}); }
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;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -51,76 +51,99 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
DenseMatrix<float> matrix = this.KernelXY; using Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size());
bool preserveAlpha = this.PreserveAlpha;
source.CopyTo(targetPixels);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int startY = interest.Y; var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha);
int endY = interest.Bottom; ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
int startX = interest.X; this.Configuration,
int endX = interest.Right; interest,
int maxY = endY - 1; in operation);
int maxX = endX - 1;
using (Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size())) Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
{ }
source.CopyTo(targetPixels);
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); /// <summary>
int width = workingRectangle.Width; /// 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>( [MethodImpl(InliningOptions.ShortMethod)]
workingRectangle, public RowIntervalOperation(
this.Configuration, Rectangle bounds,
(rows, vectorBuffer) => Buffer2D<TPixel> targetPixels,
{ Buffer2D<TPixel> sourcePixels,
Span<Vector4> vectorSpan = vectorBuffer.Span; DenseMatrix<float> kernel,
int length = vectorSpan.Length; Configuration configuration,
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); 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++) /// <inheritdoc/>
{ [MethodImpl(InliningOptions.ShortMethod)]
Span<TPixel> targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX); public void Invoke(in RowInterval rows, Span<Vector4> span)
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan); {
ref Vector4 spanRef = ref MemoryMarshal.GetReference(span);
if (preserveAlpha) for (int y = rows.Min; y < rows.Max; y++)
{ {
for (int x = 0; x < width; x++) Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X);
{ PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
DenseMatrixUtils.Convolve3(
in matrix,
source.PixelBuffer,
ref vectorSpanRef,
y,
x,
startY,
maxY,
startX,
maxX);
}
}
else
{
for (int x = 0; x < width; x++)
{
DenseMatrixUtils.Convolve4(
in matrix,
source.PixelBuffer,
ref vectorSpanRef,
y,
x,
startY,
maxY,
startX,
maxX);
}
}
PixelOperations<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 /> /// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using (var processor = new Convolution2DProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle)) using var processor = new Convolution2DProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle);
{
processor.Apply(source); processor.Apply(source);
}
} }
} }
} }

133
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs

@ -1,12 +1,10 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Processing.Processors.Filters;
@ -55,88 +53,79 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
DenseMatrix<float>[] kernels = this.Kernels.Flatten(); DenseMatrix<float>[] kernels = this.Kernels.Flatten();
int startY = this.SourceRectangle.Y; var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int endY = this.SourceRectangle.Bottom;
int startX = this.SourceRectangle.X;
int endX = this.SourceRectangle.Right;
// Align start/end positions. // We need a clean copy for each pass to start from
int minX = Math.Max(0, startX); using ImageFrame<TPixel> cleanCopy = source.Clone();
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 (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, kernels[0], true, this.Source, interest))
using (ImageFrame<TPixel> cleanCopy = source.Clone())
{ {
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) if (kernels.Length == 1)
{ {
return; return;
} }
int shiftY = startY; // Additional runs
int shiftX = startX; for (int i = 1; i < kernels.Length; i++)
{
using ImageFrame<TPixel> pass = cleanCopy.Clone();
// Reset offset if necessary. using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, kernels[i], true, this.Source, interest))
if (minX > 0)
{ {
shiftX = 0; processor.Apply(pass);
} }
if (minY > 0) var operation = new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, interest);
{ ParallelRowIterator.IterateRows(
shiftY = 0; 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. /// <inheritdoc/>
// ReSharper disable once ForCanBeConvertedToForeach [MethodImpl(InliningOptions.ShortMethod)]
for (int i = 1; i < kernels.Length; i++) 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)) // Grab the max components of the two pixels
{ ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, x);
processor.Apply(pass); ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, x);
}
var pixelValue = Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4());
Buffer2D<TPixel> passPixels = pass.PixelBuffer;
Buffer2D<TPixel> targetPixels = source.PixelBuffer; currentTargetPixel.FromVector4(pixelValue);
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);
}
}
});
} }
} }
} }

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

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

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

@ -44,10 +44,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using (var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle)) using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle);
{
processor.Apply(source); 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Drawing 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."); "Cannot draw image because the source image does not overlap the target image.");
} }
ParallelHelper.IterateRows( var operation = new RowIntervalOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity);
workingRect, ParallelRowIterator.IterateRows(
configuration, 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 = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width);
{ Span<TPixelFg> foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width);
Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width); this.blender.Blend<TPixelFg>(this.configuration, background, background, foreground, this.opacity);
Span<TPixelFg> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); }
blender.Blend<TPixelFg>(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.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -45,122 +43,145 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
throw new ArgumentOutOfRangeException(nameof(brushSize)); 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()); using Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size());
source.CopyTo(targetPixels); source.CopyTo(targetPixels);
var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels);
ParallelHelper.IterateRows( ParallelRowIterator.IterateRows(
workingRect,
this.Configuration, this.Configuration,
(rows) => this.SourceRectangle,
{ in operation);
/* 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);
Span<Vector4> targetRowVector4Span = targetRowBuffer.Memory.Span; Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
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);
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++) /// <inheritdoc/>
{ [MethodImpl(InliningOptions.ShortMethod)]
int maxIntensity = 0; public void Invoke(in RowInterval rows)
int maxIndex = 0; {
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 PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span);
bins.Memory.Span.Clear();
for (int fy = 0; fy <= radius; fy++) for (int x = this.bounds.X; x < this.bounds.Right; x++)
{ {
int fyr = fy - radius; int maxIntensity = 0;
int offsetY = y + fyr; 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++) offsetY = offsetY.Clamp(0, maxY);
{
int fxr = fx - radius;
int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX);
var vector = sourceOffsetRow[offsetX].ToVector4(); Span<TPixel> sourceOffsetRow = this.source.GetPixelRowSpan(offsetY);
float sourceRed = vector.X; for (int fx = 0; fx <= this.radius; fx++)
float sourceBlue = vector.Z; {
float sourceGreen = vector.Y; 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)++; float sourceRed = vector.X;
Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; float sourceBlue = vector.Z;
Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; float sourceGreen = vector.Y;
Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen;
if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1));
{
maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity);
maxIndex = currentIntensity;
}
}
float red = MathF.Abs(Unsafe.Add(ref redBinRef, maxIndex) / maxIntensity); Unsafe.Add(ref intensityBinRef, currentIntensity)++;
float blue = MathF.Abs(Unsafe.Add(ref blueBinRef, maxIndex) / maxIntensity); Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed;
float green = MathF.Abs(Unsafe.Add(ref greenBinRef, maxIndex) / maxIntensity); Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue;
float alpha = sourceRowVector4Span[x].W; 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Effects namespace SixLabors.ImageSharp.Processing.Processors.Effects
@ -34,6 +37,31 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Effects 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> /// <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) public PixelateProcessor(Configuration configuration, PixelateProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ => this.definition = definition;
this.definition = definition;
}
private int Size => this.definition.Size; private int Size => this.definition.Size;
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
if (this.Size <= 0 || this.Size > source.Height || this.Size > source.Width) var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
{
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;
int size = this.Size; int size = this.Size;
int offset = this.Size / 2;
// Align start/end positions. Guard.MustBeBetweenOrEqualTo(size, 0, interest.Width, nameof(size));
int minX = Math.Max(0, startX); Guard.MustBeBetweenOrEqualTo(size, 0, interest.Height, nameof(size));
int maxX = Math.Min(source.Width, endX);
int minY = Math.Max(0, startY);
int maxY = Math.Min(source.Height, endY);
// Reset offset if necessary. // Get the range on the y-plane to choose from.
if (minX > 0) // 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. for (int x = this.minX; x < this.maxX; x += this.size)
IEnumerable<int> range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, 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( // For each pixel in the pixelate size, set it to the centre color.
range, for (int oY = y; oY < y + this.size && oY < this.maxY; oY++)
this.Configuration.GetParallelOptions(),
y =>
{ {
int offsetY = y - startY; for (int oX = x; oX < x + this.size && oX < this.maxX; oX++)
int offsetPy = offset;
// Make sure that the offset is within the boundary of the image.
while (offsetY + offsetPy >= maxY)
{ {
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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Effects namespace SixLabors.ImageSharp.Processing.Processors.Effects
@ -34,6 +37,31 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> 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;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Filters namespace SixLabors.ImageSharp.Processing.Processors.Filters
@ -35,27 +36,51 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); 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; ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
interest,
this.Configuration, this.Configuration,
(rows, vectorBuffer) => interest,
{ in operation);
for (int y = rows.Min; y < rows.Max; y++) }
{
Span<Vector4> vectorSpan = vectorBuffer.Span; /// <summary>
int length = vectorSpan.Length; /// A <see langword="struct"/> implementing the convolution logic for <see cref="FilterProcessor{TPixel}"/>.
Span<TPixel> rowSpan = source.GetPixelRowSpan(y).Slice(startX, length); /// </summary>
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, rowSpan, vectorSpan); private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4>
{
Vector4Utils.Transform(vectorSpan, ref matrix); private readonly int startX;
private readonly ImageFrame<TPixel> source;
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.Configuration, vectorSpan, rowSpan); 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.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -81,56 +80,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
yStart += tileHeight; yStart += tileHeight;
} }
Parallel.For( var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source);
0, ParallelRowIterator.IterateRows(
tileYStartPositions.Count, this.Configuration,
new ParallelOptions { MaxDegreeOfParallelism = this.Configuration.MaxDegreeOfParallelism }, new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count),
index => in operation);
{
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;
}
});
ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); 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) private static float LinearInterpolation(float left, float right, float t)
=> left + ((right - left) * 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> /// <summary>
/// Contains the results of the cumulative distribution function for all tiles. /// Contains the results of the cumulative distribution function for all tiles.
/// </summary> /// </summary>
@ -431,7 +474,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
private readonly Buffer2D<int> cdfLutBuffer2D; private readonly Buffer2D<int> cdfLutBuffer2D;
private readonly int pixelsInTile; private readonly int pixelsInTile;
private readonly int sourceWidth; private readonly int sourceWidth;
private readonly int sourceHeight;
private readonly int tileWidth; private readonly int tileWidth;
private readonly int tileHeight; private readonly int tileHeight;
private readonly int luminanceLevels; private readonly int luminanceLevels;
@ -453,7 +495,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
this.cdfMinBuffer2D = this.memoryAllocator.Allocate2D<int>(tileCountX, tileCountY); this.cdfMinBuffer2D = this.memoryAllocator.Allocate2D<int>(tileCountX, tileCountY);
this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D<int>(tileCountX * luminanceLevels, tileCountY); this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D<int>(tileCountX * luminanceLevels, tileCountY);
this.sourceWidth = sourceWidth; this.sourceWidth = sourceWidth;
this.sourceHeight = sourceHeight;
this.tileWidth = tileWidth; this.tileWidth = tileWidth;
this.tileHeight = tileHeight; this.tileHeight = tileHeight;
this.pixelsInTile = tileWidth * tileHeight; this.pixelsInTile = tileWidth * tileHeight;
@ -470,57 +511,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public void CalculateLookupTables(ImageFrame<TPixel> source, HistogramEqualizationProcessor<TPixel> processor) public void CalculateLookupTables(ImageFrame<TPixel> source, HistogramEqualizationProcessor<TPixel> processor)
{ {
int sourceWidth = this.sourceWidth; var operation = new RowIntervalOperation(
int sourceHeight = this.sourceHeight; processor,
int tileWidth = this.tileWidth; this.memoryAllocator,
int tileHeight = this.tileHeight; this.cdfMinBuffer2D,
int luminanceLevels = this.luminanceLevels; this.cdfLutBuffer2D,
this.tileYStartPositions,
Parallel.For( this.tileWidth,
0, this.tileHeight,
this.tileYStartPositions.Count, this.luminanceLevels,
new ParallelOptions { MaxDegreeOfParallelism = this.configuration.MaxDegreeOfParallelism }, source);
index =>
{ ParallelRowIterator.IterateRows(
int cdfX = 0; this.configuration,
int cdfY = this.tileYStartPositions[index].cdfY; new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count),
int y = this.tileYStartPositions[index].y; in operation);
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++;
}
}
});
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -548,6 +553,93 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
this.cdfMinBuffer2D.Dispose(); this.cdfMinBuffer2D.Dispose();
this.cdfLutBuffer2D.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; int halfTileWidth = halfTileHeight;
var slidingWindowInfos = new SlidingWindowInfos(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixelInTile); var slidingWindowInfos = new SlidingWindowInfos(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixelInTile);
using (Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height)) // 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.
// Process the inner tiles, which do not require to check the borders. using Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height);
Parallel.For(
halfTileWidth, // Process the inner tiles, which do not require to check the borders.
source.Width - halfTileWidth, var innerOperation = new SlidingWindowOperation(
parallelOptions, this.Configuration,
this.ProcessSlidingWindow( this,
source, source,
memoryAllocator, memoryAllocator,
targetPixels, targetPixels,
slidingWindowInfos, slidingWindowInfos,
yStart: halfTileHeight, yStart: halfTileHeight,
yEnd: source.Height - halfTileHeight, yEnd: source.Height - halfTileHeight,
useFastPath: true, useFastPath: true);
this.Configuration));
Parallel.For(
// Process the left border of the image. halfTileWidth,
Parallel.For( source.Width - halfTileWidth,
0, parallelOptions,
halfTileWidth, innerOperation.Invoke);
parallelOptions,
this.ProcessSlidingWindow( // Process the left border of the image.
source, var leftBorderOperation = new SlidingWindowOperation(
memoryAllocator, this.Configuration,
targetPixels, this,
slidingWindowInfos, source,
yStart: 0, memoryAllocator,
yEnd: source.Height, targetPixels,
useFastPath: false, slidingWindowInfos,
this.Configuration)); yStart: 0,
yEnd: source.Height,
// Process the right border of the image. useFastPath: false);
Parallel.For(
source.Width - halfTileWidth, Parallel.For(
source.Width, 0,
parallelOptions, halfTileWidth,
this.ProcessSlidingWindow( parallelOptions,
source, leftBorderOperation.Invoke);
memoryAllocator,
targetPixels, // Process the right border of the image.
slidingWindowInfos, var rightBorderOperation = new SlidingWindowOperation(
yStart: 0, this.Configuration,
yEnd: source.Height, this,
useFastPath: false, source,
this.Configuration)); memoryAllocator,
targetPixels,
// Process the top border of the image. slidingWindowInfos,
Parallel.For( yStart: 0,
halfTileWidth, yEnd: source.Height,
source.Width - halfTileWidth, useFastPath: false);
parallelOptions,
this.ProcessSlidingWindow( Parallel.For(
source, source.Width - halfTileWidth,
memoryAllocator, source.Width,
targetPixels, parallelOptions,
slidingWindowInfos, rightBorderOperation.Invoke);
yStart: 0,
yEnd: halfTileHeight, // Process the top border of the image.
useFastPath: false, var topBorderOperation = new SlidingWindowOperation(
this.Configuration)); this.Configuration,
this,
// Process the bottom border of the image. source,
Parallel.For( memoryAllocator,
halfTileWidth, targetPixels,
source.Width - halfTileWidth, slidingWindowInfos,
parallelOptions, yStart: 0,
this.ProcessSlidingWindow( yEnd: halfTileHeight,
source, useFastPath: false);
memoryAllocator,
targetPixels, Parallel.For(
slidingWindowInfos, halfTileWidth,
yStart: source.Height - halfTileHeight, source.Width - halfTileWidth,
yEnd: source.Height, parallelOptions,
useFastPath: false, topBorderOperation.Invoke);
this.Configuration));
// Process the bottom border of the image.
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels); var bottomBorderOperation = new SlidingWindowOperation(
} this.Configuration,
} this,
source,
/// <summary> memoryAllocator,
/// Applies the sliding window equalization to one column of the image. The window is moved from top to bottom. targetPixels,
/// Moving the window one pixel down requires to remove one row from the top of the window from the histogram and slidingWindowInfos,
/// adding a new row at the bottom. yStart: source.Height - halfTileHeight,
/// </summary> yEnd: source.Height,
/// <param name="source">The source image.</param> useFastPath: false);
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="targetPixels">The target pixels.</param> Parallel.For(
/// <param name="swInfos"><see cref="SlidingWindowInfos"/> about the sliding window dimensions.</param> halfTileWidth,
/// <param name="yStart">The y start position.</param> source.Width - halfTileWidth,
/// <param name="yEnd">The y end position.</param> parallelOptions,
/// <param name="useFastPath">if set to true the borders of the image will not be checked.</param> bottomBorderOperation.Invoke);
/// <param name="configuration">The configuration.</param>
/// <returns>Action Delegate.</returns> Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
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);
}
}
};
} }
/// <summary> /// <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 private class SlidingWindowInfos
{ {
public SlidingWindowInfos(int tileWidth, int tileHeight, int halfTileWidth, int halfTileHeight, int pixelInTile) 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; 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.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -49,64 +47,125 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{ {
MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator;
int numberOfPixels = source.Width * source.Height; 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)) /// <inheritdoc/>
using (IMemoryOwner<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{ {
// Build the histogram of the grayscale levels. ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan());
ParallelHelper.IterateRows( for (int y = rows.Min; y < rows.Max; y++)
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)
{ {
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. /// <inheritdoc/>
int cdfMin = this.CalculateCdf( [MethodImpl(InliningOptions.ShortMethod)]
ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), public void Invoke(in RowInterval rows)
ref MemoryMarshal.GetReference(histogram), {
histogram.Length - 1); ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan());
for (int y = rows.Min; y < rows.Max; y++)
float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; {
ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y));
// Apply the cdf to each pixel of the image
ParallelHelper.IterateRows( for (int x = 0; x < this.bounds.Width; x++)
workingRect, {
this.Configuration, ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x);
rows => int luminance = GetLuminance(pixel, this.luminanceLevels);
{ float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin;
ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
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));
}
}
});
} }
} }
} }

114
src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs

@ -3,9 +3,8 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; 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> /// <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) public BackgroundColorProcessor(Configuration configuration, BackgroundColorProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ => this.definition = definition;
this.definition = definition;
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
@ -39,65 +36,70 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
TPixel color = this.definition.Color.ToPixel<TPixel>(); TPixel color = this.definition.Color.ToPixel<TPixel>();
GraphicsOptions graphicsOptions = this.definition.GraphicsOptions; GraphicsOptions graphicsOptions = this.definition.GraphicsOptions;
int startY = this.SourceRectangle.Y; var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
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 workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
MemoryAllocator memoryAllocator = configuration.MemoryAllocator; MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
using (IMemoryOwner<TPixel> colors = memoryAllocator.Allocate<TPixel>(width)) using IMemoryOwner<TPixel> colors = memoryAllocator.Allocate<TPixel>(interest.Width);
using (IMemoryOwner<float> amount = memoryAllocator.Allocate<float>(width)) using IMemoryOwner<float> amount = memoryAllocator.Allocate<float>(interest.Width);
{
// Be careful! Do not capture colorSpan & amountSpan in the lambda below!
Span<TPixel> colorSpan = colors.GetSpan();
Span<float> amountSpan = amount.GetSpan();
colorSpan.Fill(color); colors.GetSpan().Fill(color);
amountSpan.Fill(graphicsOptions.BlendPercentage); amount.GetSpan().Fill(graphicsOptions.BlendPercentage);
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(graphicsOptions); PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(graphicsOptions);
ParallelHelper.IterateRows( var operation = new RowIntervalOperation(configuration, interest, blender, amount, colors, source);
workingRect, ParallelRowIterator.IterateRows(
configuration, configuration,
rows => interest,
{ in operation);
for (int y = rows.Min; y < rows.Max; y++) }
{
Span<TPixel> destination =
source.GetPixelRowSpan(y - startY).Slice(minX - startX, width);
// This switched color & destination in the 2nd and 3rd places because we are applying the target color under the current one private readonly struct RowIntervalOperation : IRowIntervalOperation
blender.Blend( {
configuration, private readonly Configuration configuration;
destination, private readonly Rectangle bounds;
colors.GetSpan(), private readonly PixelBlender<TPixel> blender;
destination, private readonly IMemoryOwner<float> amount;
amount.GetSpan()); 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;
using System.Buffers; using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -39,78 +38,84 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) 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>(); 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 float maxDistance = finalRadius > 0
? MathF.Min(finalRadius, this.SourceRectangle.Width * .5F) ? MathF.Min(finalRadius, interest.Width * .5F)
: this.SourceRectangle.Width * .5F; : interest.Width * .5F;
// Align start/end positions. Configuration configuration = this.Configuration;
int minX = Math.Max(0, startX); MemoryAllocator allocator = configuration.MemoryAllocator;
int maxX = Math.Min(source.Width, endX);
int minY = Math.Max(0, startY);
int maxY = Math.Min(source.Height, endY);
// Reset offset if necessary. using IMemoryOwner<TPixel> rowColors = allocator.Allocate<TPixel>(interest.Width);
if (minX > 0) rowColors.GetSpan().Fill(glowColor);
{
startX = 0;
}
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; [MethodImpl(InliningOptions.ShortMethod)]
int offsetX = minX - startX; public void Invoke(in RowInterval rows, Span<float> span)
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))
{ {
rowColors.GetSpan().Fill(glowColor); Span<TPixel> colorSpan = this.colors.GetSpan();
ParallelHelper.IterateRowsWithTempBuffer<float>( for (int y = rows.Min; y < rows.Max; y++)
workingRect, {
configuration, for (int i = 0; i < this.bounds.Width; i++)
(rows, amounts) => {
{ float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y));
Span<float> amountsSpan = amounts.Span; span[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1);
}
for (int y = rows.Min; y < rows.Max; y++)
{ Span<TPixel> destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
int offsetY = y - startY;
this.blender.Blend(
for (int i = 0; i < width; i++) this.configuration,
{ destination,
float distance = Vector2.Distance(center, new Vector2(i + offsetX, offsetY)); destination,
amountsSpan[i] = colorSpan,
(blendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); span);
} }
Span<TPixel> destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width);
this.blender.Blend(
configuration,
destination,
destination,
rowColors.GetSpan(),
amountsSpan);
}
});
} }
} }
} }

140
src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs

@ -4,9 +4,8 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -20,7 +19,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly PixelBlender<TPixel> blender; private readonly PixelBlender<TPixel> blender;
private readonly VignetteProcessor definition; private readonly VignetteProcessor definition;
/// <summary> /// <summary>
@ -40,80 +38,92 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) 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>(); 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 float rX = finalRadiusX > 0
? MathF.Min(finalRadiusX, this.SourceRectangle.Width * .5F) ? MathF.Min(finalRadiusX, interest.Width * .5F)
: this.SourceRectangle.Width * .5F; : interest.Width * .5F;
float rY = finalRadiusY > 0 float rY = finalRadiusY > 0
? MathF.Min(finalRadiusY, this.SourceRectangle.Height * .5F) ? MathF.Min(finalRadiusY, interest.Height * .5F)
: this.SourceRectangle.Height * .5F; : interest.Height * .5F;
float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY));
// Align start/end positions. Configuration configuration = this.Configuration;
int minX = Math.Max(0, startX); MemoryAllocator allocator = configuration.MemoryAllocator;
int maxX = Math.Min(source.Width, endX);
int minY = Math.Max(0, startY);
int maxY = Math.Min(source.Height, endY);
// Reset offset if necessary. using IMemoryOwner<TPixel> rowColors = allocator.Allocate<TPixel>(interest.Width);
if (minX > 0) rowColors.GetSpan().Fill(vignetteColor);
{
startX = 0; 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; [MethodImpl(InliningOptions.ShortMethod)]
int offsetX = minX - startX; public void Invoke(in RowInterval rows, Span<float> span)
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))
{ {
rowColors.GetSpan().Fill(vignetteColor); Span<TPixel> colorSpan = this.colors.GetSpan();
ParallelHelper.IterateRowsWithTempBuffer<float>( for (int y = rows.Min; y < rows.Max; y++)
workingRect, {
configuration, for (int i = 0; i < this.bounds.Width; i++)
(rows, amounts) => {
{ float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y));
Span<float> amountsSpan = amounts.Span; span[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1);
}
for (int y = rows.Min; y < rows.Max; y++)
{ Span<TPixel> destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
int offsetY = y - startY;
this.blender.Blend(
for (int i = 0; i < width; i++) this.configuration,
{ destination,
float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); destination,
amountsSpan[i] = (blendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); colorSpan,
} span);
}
Span<TPixel> destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width);
this.blender.Blend(
configuration,
destination,
destination,
rowColors.GetSpan(),
amountsSpan);
}
});
} }
} }
} }

56
src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs

@ -2,8 +2,9 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
@ -35,27 +36,50 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(configuration)) using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(configuration);
using (IQuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(source)) using IQuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(source);
{
int paletteCount = quantized.Palette.Length - 1;
// Not parallel to remove "quantized" closure allocation. var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized);
// We can operate directly on the source here as we've already read it to get the ParallelRowIterator.IterateRows(
// quantized result configuration,
for (int y = 0; y < source.Height; y++) this.SourceRectangle,
{ in operation);
Span<TPixel> row = source.GetPixelRowSpan(y); }
ReadOnlySpan<byte> quantizedPixelSpan = quantized.GetPixelSpan();
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; 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;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -16,8 +17,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel> internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private Size targetSize; private readonly Size targetSize;
private Matrix3x2 transformMatrix; private readonly Matrix3x2 transformMatrix;
private readonly IResampler resampler; private readonly IResampler resampler;
/// <summary> /// <summary>
@ -49,7 +50,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
int width = this.targetSize.Width; int width = this.targetSize.Width;
Rectangle sourceBounds = this.SourceRectangle;
var targetBounds = new Rectangle(Point.Empty, this.targetSize); var targetBounds = new Rectangle(Point.Empty, this.targetSize);
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
@ -58,70 +58,130 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (this.resampler is NearestNeighborResampler) if (this.resampler is NearestNeighborResampler)
{ {
ParallelHelper.IterateRows( var nnOperation = new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination);
targetBounds, ParallelRowIterator.IterateRows(
configuration, configuration,
rows => targetBounds,
{ in nnOperation);
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];
}
}
}
});
return; 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>( this.bounds = bounds;
targetBounds, this.matrix = matrix;
configuration, this.maxX = maxX;
(rows, vectorBuffer) => 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; destRow[x] = this.source[point.X, point.Y];
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);
}
});
} }
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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -15,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class CropProcessor<TPixel> : TransformProcessor<TPixel> internal class CropProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private Rectangle cropRectangle; private readonly Rectangle cropRectangle;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CropProcessor{TPixel}"/> class. /// 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; Rectangle bounds = this.cropRectangle;
// Copying is cheap, we should process more pixels per task: // Copying is cheap, we should process more pixels per task:
ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration) ParallelExecutionSettings parallelSettings =
.MultiplyMinimumPixelsPerTask(4); ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4);
ParallelHelper.IterateRows( var operation = new RowIntervalOperation(bounds, source, destination);
ParallelRowIterator.IterateRows(
bounds, bounds,
parallelSettings, in parallelSettings,
rows => in operation);
{ }
for (int y = rows.Min; y < rows.Max; y++)
{ /// <summary>
Span<TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left); /// A <see langword="struct"/> implementing the processor logic for <see cref="CropProcessor{T}"/>.
Span<TPixel> targetRow = destination.GetPixelRowSpan(y - bounds.Top); /// </summary>
sourceRow.Slice(0, bounds.Width).CopyTo(targetRow); 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;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -55,20 +55,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private void FlipX(ImageFrame<TPixel> source, Configuration configuration) private void FlipX(ImageFrame<TPixel> source, Configuration configuration)
{ {
int height = source.Height; 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; int yBottom = height - yTop - 1;
Span<TPixel> topRow = source.GetPixelRowSpan(yBottom);
for (int yTop = 0; yTop < height / 2; yTop++) Span<TPixel> bottomRow = source.GetPixelRowSpan(yTop);
{ topRow.CopyTo(temp);
int yBottom = height - yTop - 1; bottomRow.CopyTo(topRow);
Span<TPixel> topRow = source.GetPixelRowSpan(yBottom); temp.CopyTo(bottomRow);
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> /// <param name="configuration">The configuration.</param>
private void FlipY(ImageFrame<TPixel> source, Configuration configuration) private void FlipY(ImageFrame<TPixel> source, Configuration configuration)
{ {
ParallelHelper.IterateRows( var operation = new RowIntervalOperation(source);
source.Bounds(), ParallelRowIterator.IterateRows(
configuration, configuration,
rows => source.Bounds(),
{ in operation);
for (int y = rows.Min; y < rows.Max; y++) }
{
source.GetPixelRowSpan(y).Reverse(); 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;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -17,9 +17,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel> internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private Size targetSize; private readonly Size targetSize;
private readonly IResampler resampler; private readonly IResampler resampler;
private Matrix4x4 transformMatrix; private readonly Matrix4x4 transformMatrix;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class. /// 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; int width = this.targetSize.Width;
Rectangle sourceBounds = this.SourceRectangle;
var targetBounds = new Rectangle(Point.Empty, this.targetSize); var targetBounds = new Rectangle(Point.Empty, this.targetSize);
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
@ -59,73 +58,126 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (this.resampler is NearestNeighborResampler) if (this.resampler is NearestNeighborResampler)
{ {
ParallelHelper.IterateRows( Rectangle sourceBounds = this.SourceRectangle;
targetBounds,
var nnOperation = new NearestNeighborRowIntervalOperation(sourceBounds, ref matrix, width, source, destination);
ParallelRowIterator.IterateRows(
configuration, configuration,
rows => targetBounds,
{ in nnOperation);
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];
}
}
}
});
return; 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>( this.bounds = bounds;
targetBounds, this.matrix = matrix;
configuration, this.maxX = maxX;
(rows, vectorBuffer) => 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; destRow[x] = this.source[px, py];
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);
private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4>
for (int x = 0; x < width; x++) {
{ private readonly Configuration configuration;
// Use the single precision position to calculate correct bounding pixels private readonly TransformKernelMap kernelMap;
// otherwise we get rogue pixels outside of the bounds. private readonly Matrix4x4 matrix;
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); private readonly int maxX;
kernel.Convolve( private readonly ImageFrame<TPixel> source;
point, private readonly ImageFrame<TPixel> destination;
x,
ref ySpanRef, [MethodImpl(InliningOptions.ShortMethod)]
ref xSpanRef, public RowIntervalOperation(
source.PixelBuffer, Configuration configuration,
vectorSpan); TransformKernelMap kernelMap,
} ref Matrix4x4 matrix,
int maxX,
PixelOperations<TPixel>.Instance.FromVector4Destructive( ImageFrame<TPixel> source,
configuration, ImageFrame<TPixel> destination)
vectorSpan, {
targetRowSpan); 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -248,4 +248,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return new ResizeKernel(left, rowPtr, length); 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -24,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly int targetWidth; private readonly int targetWidth;
private readonly int targetHeight; private readonly int targetHeight;
private readonly IResampler resampler; private readonly IResampler resampler;
private Rectangle targetRectangle; private readonly Rectangle targetRectangle;
private readonly bool compand; private readonly bool compand;
// The following fields are not immutable but are optionally created on demand. // 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; Configuration configuration = this.Configuration;
// Handle resize dimensions identical to the original // 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 // The cloned will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
@ -86,14 +87,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int width = this.targetWidth; int width = this.targetWidth;
int height = this.targetHeight; int height = this.targetHeight;
int sourceX = sourceRectangle.X; var interest = Rectangle.Intersect(this.targetRectangle, new Rectangle(0, 0, width, height));
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));
if (this.resampler is NearestNeighborResampler) if (this.resampler is NearestNeighborResampler)
{ {
@ -101,24 +95,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height;
ParallelHelper.IterateRows( var operation = new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination);
targetWorkingRect, ParallelRowIterator.IterateRows(
configuration, configuration,
rows => interest,
{ in operation);
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)];
}
}
});
return; return;
} }
@ -137,12 +118,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.horizontalKernelMap, this.horizontalKernelMap,
this.verticalKernelMap, this.verticalKernelMap,
width, width,
targetWorkingRect, interest,
this.targetRectangle.Location)) this.targetRectangle.Location))
{ {
worker.Initialize(); worker.Initialize();
var workingInterval = new RowInterval(targetWorkingRect.Top, targetWorkingRect.Bottom); var workingInterval = new RowInterval(interest.Top, interest.Bottom);
worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); worker.FillDestinationPixels(workingInterval, destination.PixelBuffer);
} }
} }
@ -166,5 +147,56 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.isDisposed = true; this.isDisposed = true;
base.Dispose(disposing); 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. /// 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". /// For more details, and visual explanation, see "ResizeWorker.pptx".
/// </summary> /// </summary>
internal class ResizeWorker<TPixel> : IDisposable internal sealed class ResizeWorker<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly Buffer2D<Vector4> transposedFirstPassBuffer; 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -17,6 +17,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class RotateProcessor<TPixel> : AffineTransformProcessor<TPixel> internal class RotateProcessor<TPixel> : AffineTransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly float degrees;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class.
/// </summary> /// </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> /// <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) public RotateProcessor(Configuration configuration, RotateProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, definition, source, 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/> /// <inheritdoc/>
protected override void AfterImageApply(Image<TPixel> destination) protected override void AfterImageApply(Image<TPixel> destination)
@ -41,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return; return;
} }
if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon) if (MathF.Abs(WrapDegrees(this.degrees)) < Constants.Epsilon)
{ {
// No need to do anything so return. // No need to do anything so return.
return; return;
@ -52,17 +61,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
base.AfterImageApply(destination); 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> /// <summary>
/// Wraps a given angle in degrees so that it falls withing the 0-360 degree range /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range
/// </summary> /// </summary>
@ -95,7 +93,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Configuration configuration) Configuration configuration)
{ {
// Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. // 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) if (MathF.Abs(degrees) < Constants.Epsilon)
{ {
@ -133,25 +131,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
private void Rotate180(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) private void Rotate180(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration)
{ {
int width = source.Width; var operation = new Rotate180RowIntervalOperation(source.Width, source.Height, source, destination);
int height = source.Height; ParallelRowIterator.IterateRows(
ParallelHelper.IterateRows(
source.Bounds(),
configuration, configuration,
rows => source.Bounds(),
{ in operation);
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];
}
}
});
} }
/// <summary> /// <summary>
@ -162,31 +146,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
private void Rotate270(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) private void Rotate270(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration)
{ {
int width = source.Width; var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination);
int height = source.Height; ParallelRowIterator.IterateRows(
Rectangle destinationBounds = destination.Bounds();
ParallelHelper.IterateRows(
source.Bounds(),
configuration, configuration,
rows => source.Bounds(),
{ in operation);
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];
}
}
}
});
} }
/// <summary> /// <summary>
@ -197,28 +161,132 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
private void Rotate90(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) private void Rotate90(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration)
{ {
int width = source.Width; var operation = new Rotate90RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination);
int height = source.Height; ParallelRowIterator.IterateRows(
Rectangle destinationBounds = destination.Bounds();
ParallelHelper.IterateRows(
source.Bounds(),
configuration, 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); this.destination[newX, x] = sourceRow[x];
int newX = height - y - 1;
for (int x = 0; x < width; x++)
{
if (destinationBounds.Contains(newX, x))
{
destination[newX, x] = sourceRow[x];
}
}
} }
}); }
}
}
} }
} }
} }

1
tests/Directory.Build.targets

@ -26,6 +26,7 @@
<ItemGroup> <ItemGroup>
<!--Test Dependencies--> <!--Test Dependencies-->
<PackageReference Update="BenchmarkDotNet" Version="0.12.0" /> <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="Colourful" Version="2.0.3" />
<PackageReference Update="coverlet.collector" Version="1.2.0" PrivateAssets="All"/> <PackageReference Update="coverlet.collector" Version="1.2.0" PrivateAssets="All"/>
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.14.4" /> <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. // Licensed under the Apache License, Version 2.0.
using System.Drawing; using System.Drawing;
@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
using (var image = new Image<Rgba32>(400, 400)) using (var image = new Image<Rgba32>(400, 400))
{ {
image[200, 200] = Rgba32.White; image[200, 200] = Color.White;
return image[200, 200]; return image[200, 200];
} }
} }

22
tests/ImageSharp.Benchmarks/Config.cs

@ -1,6 +1,10 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // 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.Configs;
using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Environments; using BenchmarkDotNet.Environments;
@ -13,6 +17,14 @@ namespace SixLabors.ImageSharp.Benchmarks
public Config() public Config()
{ {
this.Add(MemoryDiagnoser.Default); this.Add(MemoryDiagnoser.Default);
#if Windows_NT
if (this.IsElevated)
{
this.Add(new NativeMemoryProfiler());
}
#endif
} }
public class ShortClr : Config public class ShortClr : Config
@ -25,5 +37,15 @@ namespace SixLabors.ImageSharp.Benchmarks
Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); 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> <ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" /> <PackageReference Include="Magick.NET-Q16-AnyCPU" />
<PackageReference Include="BenchmarkDotNet" /> <PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Condition="'$(OS)' == 'Windows_NT'" />
<PackageReference Include="Colourful" /> <PackageReference Include="Colourful" />
<PackageReference Include="Pfim" /> <PackageReference Include="Pfim" />
<PackageReference Include="System.Drawing.Common" /> <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 namespace SixLabors.ImageSharp.Benchmarks
{ {
[Config(typeof(Config.ShortClr))]
public class Crop : BenchmarkBase public class Crop : BenchmarkBase
{ {
[Benchmark(Baseline = true, Description = "System.Drawing Crop")] [Benchmark(Baseline = true, Description = "System.Drawing Crop")]

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

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
[Benchmark] [Benchmark]
public Size DoDiffuse() 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()); image.Mutate(x => x.Diffuse());

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

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
[Benchmark] [Benchmark]
public void Blur() 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()); image.Mutate(c => c.GaussianBlur());
} }

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

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
[Benchmark] [Benchmark]
public Size DoRotate() 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)); image.Mutate(x => x.Rotate(37.5F));

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

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
[Benchmark] [Benchmark]
public Size DoSkew() 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)); image.Mutate(x => x.Skew(20, 10));

138
tests/ImageSharp.Tests/Color/ColorTests.cs

@ -3,9 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests
@ -15,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void WithAlpha() 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); Color c2 = c1.WithAlpha(0.5f);
var expected = new Rgba32(111, 222, 55, 128); var expected = new Rgba32(111, 222, 55, 128);
@ -56,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests
public void ToHex() public void ToHex()
{ {
string expected = "ABCD1234"; string expected = "ABCD1234";
Color color = Color.FromHex(expected); var color = Color.ParseHex(expected);
string actual = color.ToHex(); string actual = color.ToHex();
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
@ -66,14 +64,22 @@ namespace SixLabors.ImageSharp.Tests
public void WebSafePalette_IsCorrect() public void WebSafePalette_IsCorrect()
{ {
Rgba32[] actualPalette = Color.WebSafePalette.ToArray().Select(c => (Rgba32)c).ToArray(); 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] [Fact]
public void WernerPalette_IsCorrect() public void WernerPalette_IsCorrect()
{ {
Rgba32[] actualPalette = Color.WernerPalette.ToArray().Select(c => (Rgba32)c).ToArray(); 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 public class FromHex
@ -81,28 +87,134 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void ShortHex() public void ShortHex()
{ {
Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.FromHex("#fff")); Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.ParseHex("#fff"));
Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.FromHex("fff")); Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.ParseHex("fff"));
Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32)Color.FromHex("000f")); 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] [Fact]
public void LeadingPoundIsOptional() public void LeadingPoundIsOptional()
{ {
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.FromHex("008080")); Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.ParseHex("008080"));
} }
[Fact] [Fact]
public void ThrowsOnEmpty() 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] [Fact]
public void ThrowsOnNull() 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using System;
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests
{ {
@ -10,268 +11,422 @@ namespace SixLabors.ImageSharp.Tests
/// <summary> /// <summary>
/// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4.
/// </summary> /// </summary>
public static readonly Rgba32[] WebSafeColors = public static readonly Color[] WebSafeColors =
{ {
Rgba32.AliceBlue, Color.AliceBlue,
Rgba32.AntiqueWhite, Color.AntiqueWhite,
Rgba32.Aqua, Color.Aqua,
Rgba32.Aquamarine, Color.Aquamarine,
Rgba32.Azure, Color.Azure,
Rgba32.Beige, Color.Beige,
Rgba32.Bisque, Color.Bisque,
Rgba32.Black, Color.Black,
Rgba32.BlanchedAlmond, Color.BlanchedAlmond,
Rgba32.Blue, Color.Blue,
Rgba32.BlueViolet, Color.BlueViolet,
Rgba32.Brown, Color.Brown,
Rgba32.BurlyWood, Color.BurlyWood,
Rgba32.CadetBlue, Color.CadetBlue,
Rgba32.Chartreuse, Color.Chartreuse,
Rgba32.Chocolate, Color.Chocolate,
Rgba32.Coral, Color.Coral,
Rgba32.CornflowerBlue, Color.CornflowerBlue,
Rgba32.Cornsilk, Color.Cornsilk,
Rgba32.Crimson, Color.Crimson,
Rgba32.Cyan, Color.Cyan,
Rgba32.DarkBlue, Color.DarkBlue,
Rgba32.DarkCyan, Color.DarkCyan,
Rgba32.DarkGoldenrod, Color.DarkGoldenrod,
Rgba32.DarkGray, Color.DarkGray,
Rgba32.DarkGreen, Color.DarkGreen,
Rgba32.DarkKhaki, Color.DarkKhaki,
Rgba32.DarkMagenta, Color.DarkMagenta,
Rgba32.DarkOliveGreen, Color.DarkOliveGreen,
Rgba32.DarkOrange, Color.DarkOrange,
Rgba32.DarkOrchid, Color.DarkOrchid,
Rgba32.DarkRed, Color.DarkRed,
Rgba32.DarkSalmon, Color.DarkSalmon,
Rgba32.DarkSeaGreen, Color.DarkSeaGreen,
Rgba32.DarkSlateBlue, Color.DarkSlateBlue,
Rgba32.DarkSlateGray, Color.DarkSlateGray,
Rgba32.DarkTurquoise, Color.DarkTurquoise,
Rgba32.DarkViolet, Color.DarkViolet,
Rgba32.DeepPink, Color.DeepPink,
Rgba32.DeepSkyBlue, Color.DeepSkyBlue,
Rgba32.DimGray, Color.DimGray,
Rgba32.DodgerBlue, Color.DodgerBlue,
Rgba32.Firebrick, Color.Firebrick,
Rgba32.FloralWhite, Color.FloralWhite,
Rgba32.ForestGreen, Color.ForestGreen,
Rgba32.Fuchsia, Color.Fuchsia,
Rgba32.Gainsboro, Color.Gainsboro,
Rgba32.GhostWhite, Color.GhostWhite,
Rgba32.Gold, Color.Gold,
Rgba32.Goldenrod, Color.Goldenrod,
Rgba32.Gray, Color.Gray,
Rgba32.Green, Color.Green,
Rgba32.GreenYellow, Color.GreenYellow,
Rgba32.Honeydew, Color.Honeydew,
Rgba32.HotPink, Color.HotPink,
Rgba32.IndianRed, Color.IndianRed,
Rgba32.Indigo, Color.Indigo,
Rgba32.Ivory, Color.Ivory,
Rgba32.Khaki, Color.Khaki,
Rgba32.Lavender, Color.Lavender,
Rgba32.LavenderBlush, Color.LavenderBlush,
Rgba32.LawnGreen, Color.LawnGreen,
Rgba32.LemonChiffon, Color.LemonChiffon,
Rgba32.LightBlue, Color.LightBlue,
Rgba32.LightCoral, Color.LightCoral,
Rgba32.LightCyan, Color.LightCyan,
Rgba32.LightGoldenrodYellow, Color.LightGoldenrodYellow,
Rgba32.LightGray, Color.LightGray,
Rgba32.LightGreen, Color.LightGreen,
Rgba32.LightPink, Color.LightPink,
Rgba32.LightSalmon, Color.LightSalmon,
Rgba32.LightSeaGreen, Color.LightSeaGreen,
Rgba32.LightSkyBlue, Color.LightSkyBlue,
Rgba32.LightSlateGray, Color.LightSlateGray,
Rgba32.LightSteelBlue, Color.LightSteelBlue,
Rgba32.LightYellow, Color.LightYellow,
Rgba32.Lime, Color.Lime,
Rgba32.LimeGreen, Color.LimeGreen,
Rgba32.Linen, Color.Linen,
Rgba32.Magenta, Color.Magenta,
Rgba32.Maroon, Color.Maroon,
Rgba32.MediumAquamarine, Color.MediumAquamarine,
Rgba32.MediumBlue, Color.MediumBlue,
Rgba32.MediumOrchid, Color.MediumOrchid,
Rgba32.MediumPurple, Color.MediumPurple,
Rgba32.MediumSeaGreen, Color.MediumSeaGreen,
Rgba32.MediumSlateBlue, Color.MediumSlateBlue,
Rgba32.MediumSpringGreen, Color.MediumSpringGreen,
Rgba32.MediumTurquoise, Color.MediumTurquoise,
Rgba32.MediumVioletRed, Color.MediumVioletRed,
Rgba32.MidnightBlue, Color.MidnightBlue,
Rgba32.MintCream, Color.MintCream,
Rgba32.MistyRose, Color.MistyRose,
Rgba32.Moccasin, Color.Moccasin,
Rgba32.NavajoWhite, Color.NavajoWhite,
Rgba32.Navy, Color.Navy,
Rgba32.OldLace, Color.OldLace,
Rgba32.Olive, Color.Olive,
Rgba32.OliveDrab, Color.OliveDrab,
Rgba32.Orange, Color.Orange,
Rgba32.OrangeRed, Color.OrangeRed,
Rgba32.Orchid, Color.Orchid,
Rgba32.PaleGoldenrod, Color.PaleGoldenrod,
Rgba32.PaleGreen, Color.PaleGreen,
Rgba32.PaleTurquoise, Color.PaleTurquoise,
Rgba32.PaleVioletRed, Color.PaleVioletRed,
Rgba32.PapayaWhip, Color.PapayaWhip,
Rgba32.PeachPuff, Color.PeachPuff,
Rgba32.Peru, Color.Peru,
Rgba32.Pink, Color.Pink,
Rgba32.Plum, Color.Plum,
Rgba32.PowderBlue, Color.PowderBlue,
Rgba32.Purple, Color.Purple,
Rgba32.RebeccaPurple, Color.RebeccaPurple,
Rgba32.Red, Color.Red,
Rgba32.RosyBrown, Color.RosyBrown,
Rgba32.RoyalBlue, Color.RoyalBlue,
Rgba32.SaddleBrown, Color.SaddleBrown,
Rgba32.Salmon, Color.Salmon,
Rgba32.SandyBrown, Color.SandyBrown,
Rgba32.SeaGreen, Color.SeaGreen,
Rgba32.SeaShell, Color.SeaShell,
Rgba32.Sienna, Color.Sienna,
Rgba32.Silver, Color.Silver,
Rgba32.SkyBlue, Color.SkyBlue,
Rgba32.SlateBlue, Color.SlateBlue,
Rgba32.SlateGray, Color.SlateGray,
Rgba32.Snow, Color.Snow,
Rgba32.SpringGreen, Color.SpringGreen,
Rgba32.SteelBlue, Color.SteelBlue,
Rgba32.Tan, Color.Tan,
Rgba32.Teal, Color.Teal,
Rgba32.Thistle, Color.Thistle,
Rgba32.Tomato, Color.Tomato,
Rgba32.Transparent, Color.Transparent,
Rgba32.Turquoise, Color.Turquoise,
Rgba32.Violet, Color.Violet,
Rgba32.Wheat, Color.Wheat,
Rgba32.White, Color.White,
Rgba32.WhiteSmoke, Color.WhiteSmoke,
Rgba32.Yellow, Color.Yellow,
Rgba32.YellowGreen Color.YellowGreen
}; };
/// <summary> /// <summary>
/// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. /// 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"/> /// The hex codes were collected and defined by Nicholas Rougeux <see href="https://www.c82.net/werner"/>
/// </summary> /// </summary>
public static readonly Rgba32[] WernerColors = public static readonly Color[] WernerColors =
{ {
Rgba32.FromHex("#f1e9cd"), Color.ParseHex("#f1e9cd"),
Rgba32.FromHex("#f2e7cf"), Color.ParseHex("#f2e7cf"),
Rgba32.FromHex("#ece6d0"), Color.ParseHex("#ece6d0"),
Rgba32.FromHex("#f2eacc"), Color.ParseHex("#f2eacc"),
Rgba32.FromHex("#f3e9ca"), Color.ParseHex("#f3e9ca"),
Rgba32.FromHex("#f2ebcd"), Color.ParseHex("#f2ebcd"),
Rgba32.FromHex("#e6e1c9"), Color.ParseHex("#e6e1c9"),
Rgba32.FromHex("#e2ddc6"), Color.ParseHex("#e2ddc6"),
Rgba32.FromHex("#cbc8b7"), Color.ParseHex("#cbc8b7"),
Rgba32.FromHex("#bfbbb0"), Color.ParseHex("#bfbbb0"),
Rgba32.FromHex("#bebeb3"), Color.ParseHex("#bebeb3"),
Rgba32.FromHex("#b7b5ac"), Color.ParseHex("#b7b5ac"),
Rgba32.FromHex("#bab191"), Color.ParseHex("#bab191"),
Rgba32.FromHex("#9c9d9a"), Color.ParseHex("#9c9d9a"),
Rgba32.FromHex("#8a8d84"), Color.ParseHex("#8a8d84"),
Rgba32.FromHex("#5b5c61"), Color.ParseHex("#5b5c61"),
Rgba32.FromHex("#555152"), Color.ParseHex("#555152"),
Rgba32.FromHex("#413f44"), Color.ParseHex("#413f44"),
Rgba32.FromHex("#454445"), Color.ParseHex("#454445"),
Rgba32.FromHex("#423937"), Color.ParseHex("#423937"),
Rgba32.FromHex("#433635"), Color.ParseHex("#433635"),
Rgba32.FromHex("#252024"), Color.ParseHex("#252024"),
Rgba32.FromHex("#241f20"), Color.ParseHex("#241f20"),
Rgba32.FromHex("#281f3f"), Color.ParseHex("#281f3f"),
Rgba32.FromHex("#1c1949"), Color.ParseHex("#1c1949"),
Rgba32.FromHex("#4f638d"), Color.ParseHex("#4f638d"),
Rgba32.FromHex("#383867"), Color.ParseHex("#383867"),
Rgba32.FromHex("#5c6b8f"), Color.ParseHex("#5c6b8f"),
Rgba32.FromHex("#657abb"), Color.ParseHex("#657abb"),
Rgba32.FromHex("#6f88af"), Color.ParseHex("#6f88af"),
Rgba32.FromHex("#7994b5"), Color.ParseHex("#7994b5"),
Rgba32.FromHex("#6fb5a8"), Color.ParseHex("#6fb5a8"),
Rgba32.FromHex("#719ba2"), Color.ParseHex("#719ba2"),
Rgba32.FromHex("#8aa1a6"), Color.ParseHex("#8aa1a6"),
Rgba32.FromHex("#d0d5d3"), Color.ParseHex("#d0d5d3"),
Rgba32.FromHex("#8590ae"), Color.ParseHex("#8590ae"),
Rgba32.FromHex("#3a2f52"), Color.ParseHex("#3a2f52"),
Rgba32.FromHex("#39334a"), Color.ParseHex("#39334a"),
Rgba32.FromHex("#6c6d94"), Color.ParseHex("#6c6d94"),
Rgba32.FromHex("#584c77"), Color.ParseHex("#584c77"),
Rgba32.FromHex("#533552"), Color.ParseHex("#533552"),
Rgba32.FromHex("#463759"), Color.ParseHex("#463759"),
Rgba32.FromHex("#bfbac0"), Color.ParseHex("#bfbac0"),
Rgba32.FromHex("#77747f"), Color.ParseHex("#77747f"),
Rgba32.FromHex("#4a475c"), Color.ParseHex("#4a475c"),
Rgba32.FromHex("#b8bfaf"), Color.ParseHex("#b8bfaf"),
Rgba32.FromHex("#b2b599"), Color.ParseHex("#b2b599"),
Rgba32.FromHex("#979c84"), Color.ParseHex("#979c84"),
Rgba32.FromHex("#5d6161"), Color.ParseHex("#5d6161"),
Rgba32.FromHex("#61ac86"), Color.ParseHex("#61ac86"),
Rgba32.FromHex("#a4b6a7"), Color.ParseHex("#a4b6a7"),
Rgba32.FromHex("#adba98"), Color.ParseHex("#adba98"),
Rgba32.FromHex("#93b778"), Color.ParseHex("#93b778"),
Rgba32.FromHex("#7d8c55"), Color.ParseHex("#7d8c55"),
Rgba32.FromHex("#33431e"), Color.ParseHex("#33431e"),
Rgba32.FromHex("#7c8635"), Color.ParseHex("#7c8635"),
Rgba32.FromHex("#8e9849"), Color.ParseHex("#8e9849"),
Rgba32.FromHex("#c2c190"), Color.ParseHex("#c2c190"),
Rgba32.FromHex("#67765b"), Color.ParseHex("#67765b"),
Rgba32.FromHex("#ab924b"), Color.ParseHex("#ab924b"),
Rgba32.FromHex("#c8c76f"), Color.ParseHex("#c8c76f"),
Rgba32.FromHex("#ccc050"), Color.ParseHex("#ccc050"),
Rgba32.FromHex("#ebdd99"), Color.ParseHex("#ebdd99"),
Rgba32.FromHex("#ab9649"), Color.ParseHex("#ab9649"),
Rgba32.FromHex("#dbc364"), Color.ParseHex("#dbc364"),
Rgba32.FromHex("#e6d058"), Color.ParseHex("#e6d058"),
Rgba32.FromHex("#ead665"), Color.ParseHex("#ead665"),
Rgba32.FromHex("#d09b2c"), Color.ParseHex("#d09b2c"),
Rgba32.FromHex("#a36629"), Color.ParseHex("#a36629"),
Rgba32.FromHex("#a77d35"), Color.ParseHex("#a77d35"),
Rgba32.FromHex("#f0d696"), Color.ParseHex("#f0d696"),
Rgba32.FromHex("#d7c485"), Color.ParseHex("#d7c485"),
Rgba32.FromHex("#f1d28c"), Color.ParseHex("#f1d28c"),
Rgba32.FromHex("#efcc83"), Color.ParseHex("#efcc83"),
Rgba32.FromHex("#f3daa7"), Color.ParseHex("#f3daa7"),
Rgba32.FromHex("#dfa837"), Color.ParseHex("#dfa837"),
Rgba32.FromHex("#ebbc71"), Color.ParseHex("#ebbc71"),
Rgba32.FromHex("#d17c3f"), Color.ParseHex("#d17c3f"),
Rgba32.FromHex("#92462f"), Color.ParseHex("#92462f"),
Rgba32.FromHex("#be7249"), Color.ParseHex("#be7249"),
Rgba32.FromHex("#bb603c"), Color.ParseHex("#bb603c"),
Rgba32.FromHex("#c76b4a"), Color.ParseHex("#c76b4a"),
Rgba32.FromHex("#a75536"), Color.ParseHex("#a75536"),
Rgba32.FromHex("#b63e36"), Color.ParseHex("#b63e36"),
Rgba32.FromHex("#b5493a"), Color.ParseHex("#b5493a"),
Rgba32.FromHex("#cd6d57"), Color.ParseHex("#cd6d57"),
Rgba32.FromHex("#711518"), Color.ParseHex("#711518"),
Rgba32.FromHex("#e9c49d"), Color.ParseHex("#e9c49d"),
Rgba32.FromHex("#eedac3"), Color.ParseHex("#eedac3"),
Rgba32.FromHex("#eecfbf"), Color.ParseHex("#eecfbf"),
Rgba32.FromHex("#ce536b"), Color.ParseHex("#ce536b"),
Rgba32.FromHex("#b74a70"), Color.ParseHex("#b74a70"),
Rgba32.FromHex("#b7757c"), Color.ParseHex("#b7757c"),
Rgba32.FromHex("#612741"), Color.ParseHex("#612741"),
Rgba32.FromHex("#7a4848"), Color.ParseHex("#7a4848"),
Rgba32.FromHex("#3f3033"), Color.ParseHex("#3f3033"),
Rgba32.FromHex("#8d746f"), Color.ParseHex("#8d746f"),
Rgba32.FromHex("#4d3635"), Color.ParseHex("#4d3635"),
Rgba32.FromHex("#6e3b31"), Color.ParseHex("#6e3b31"),
Rgba32.FromHex("#864735"), Color.ParseHex("#864735"),
Rgba32.FromHex("#553d3a"), Color.ParseHex("#553d3a"),
Rgba32.FromHex("#613936"), Color.ParseHex("#613936"),
Rgba32.FromHex("#7a4b3a"), Color.ParseHex("#7a4b3a"),
Rgba32.FromHex("#946943"), Color.ParseHex("#946943"),
Rgba32.FromHex("#c39e6d"), Color.ParseHex("#c39e6d"),
Rgba32.FromHex("#513e32"), Color.ParseHex("#513e32"),
Rgba32.FromHex("#8b7859"), Color.ParseHex("#8b7859"),
Rgba32.FromHex("#9b856b"), Color.ParseHex("#9b856b"),
Rgba32.FromHex("#766051"), Color.ParseHex("#766051"),
Rgba32.FromHex("#453b32") 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 (Image<Rgba32> background = provider.GetImage())
using (var overlay = new Image<Rgba32>(50, 50)) 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)); 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) public void NonOverlappingImageThrows(TestImageProvider<Rgba32> provider, int x, int y)
{ {
using (Image<Rgba32> background = provider.GetImage()) 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); 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Advanced;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Helpers 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Concurrent;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Helpers 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; private readonly ITestOutputHelper output;
public ParallelHelperTests(ITestOutputHelper output) public ParallelRowIteratorTests(ITestOutputHelper output)
{ {
this.output = output; this.output = output;
} }
@ -28,18 +28,22 @@ namespace SixLabors.ImageSharp.Tests.Helpers
/// <summary> /// <summary>
/// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength
/// </summary> /// </summary>
public static TheoryData<int, int, int, int, int> IterateRows_OverMinimumPixelsLimit_Data = public static TheoryData<int, int, int, int, int, int> IterateRows_OverMinimumPixelsLimit_Data =
new TheoryData<int, int, int, int, int> new TheoryData<int, int, int, int, int, int>
{ {
{ 1, 0, 100, -1, 100 }, { 1, 0, 100, -1, 100, 1 },
{ 2, 0, 9, 5, 4 }, { 2, 0, 9, 5, 4, 2 },
{ 4, 0, 19, 5, 4 }, { 4, 0, 19, 5, 4, 4 },
{ 2, 10, 19, 5, 4 }, { 2, 10, 19, 5, 4, 2 },
{ 4, 0, 200, 50, 50 }, { 4, 0, 200, 50, 50, 4 },
{ 4, 123, 323, 50, 50 }, { 4, 123, 323, 50, 50, 4 },
{ 4, 0, 1201, 301, 298 }, { 4, 0, 1201, 301, 298, 4 },
{ 8, 10, 236, 29, 23 } { 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] [Theory]
[MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))]
@ -48,7 +52,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int minY, int minY,
int maxY, int maxY,
int expectedStepLength, int expectedStepLength,
int expectedLastStepLength) int expectedLastStepLength,
int expectedNumberOfSteps)
{ {
var parallelSettings = new ParallelExecutionSettings( var parallelSettings = new ParallelExecutionSettings(
maxDegreeOfParallelism, maxDegreeOfParallelism,
@ -59,22 +64,26 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int actualNumberOfSteps = 0; int actualNumberOfSteps = 0;
ParallelHelper.IterateRows( void RowAction(RowInterval rows)
rectangle, {
parallelSettings, Assert.True(rows.Min >= minY);
rows => Assert.True(rows.Max <= maxY);
{
Assert.True(rows.Min >= minY);
Assert.True(rows.Max <= maxY);
int step = rows.Max - rows.Min; int step = rows.Max - rows.Min;
int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength;
Interlocked.Increment(ref actualNumberOfSteps); Interlocked.Increment(ref actualNumberOfSteps);
Assert.Equal(expected, step); 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] [Theory]
@ -84,7 +93,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int minY, int minY,
int maxY, int maxY,
int expectedStepLength, int expectedStepLength,
int expectedLastStepLength) int expectedLastStepLength,
int expectedNumberOfSteps)
{ {
var parallelSettings = new ParallelExecutionSettings( var parallelSettings = new ParallelExecutionSettings(
maxDegreeOfParallelism, maxDegreeOfParallelism,
@ -96,16 +106,20 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray();
var actualData = new int[maxY]; 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, rectangle,
parallelSettings, in parallelSettings,
rows => in operation);
{
for (int y = rows.Min; y < rows.Max; y++)
{
actualData[y] = y;
}
});
Assert.Equal(expectedData, actualData); Assert.Equal(expectedData, actualData);
} }
@ -117,7 +131,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int minY, int minY,
int maxY, int maxY,
int expectedStepLength, int expectedStepLength,
int expectedLastStepLength) int expectedLastStepLength,
int expectedNumberOfSteps)
{ {
var parallelSettings = new ParallelExecutionSettings( var parallelSettings = new ParallelExecutionSettings(
maxDegreeOfParallelism, maxDegreeOfParallelism,
@ -126,30 +141,28 @@ namespace SixLabors.ImageSharp.Tests.Helpers
var rectangle = new Rectangle(0, minY, 10, maxY - minY); var rectangle = new Rectangle(0, minY, 10, maxY - minY);
var bufferHashes = new ConcurrentBag<int>();
int actualNumberOfSteps = 0; 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; Interlocked.Increment(ref actualNumberOfSteps);
int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; Assert.Equal(expected, step);
}
Interlocked.Increment(ref actualNumberOfSteps); var operation = new TestRowIntervalOperation<Vector4>(RowAction);
Assert.Equal(expected, step);
});
Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); ParallelRowIterator.IterateRows<TestRowIntervalOperation<Vector4>, Vector4>(
rectangle,
in parallelSettings,
in operation);
int numberOfDifferentBuffers = bufferHashes.Distinct().Count(); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers);
} }
[Theory] [Theory]
@ -159,7 +172,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int minY, int minY,
int maxY, int maxY,
int expectedStepLength, int expectedStepLength,
int expectedLastStepLength) int expectedLastStepLength,
int expectedNumberOfSteps)
{ {
var parallelSettings = new ParallelExecutionSettings( var parallelSettings = new ParallelExecutionSettings(
maxDegreeOfParallelism, maxDegreeOfParallelism,
@ -171,31 +185,35 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray();
var actualData = new int[maxY]; 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, rectangle,
parallelSettings, in parallelSettings,
(RowInterval rows, Memory<Vector4> buffer) => in operation);
{
for (int y = rows.Min; y < rows.Max; y++)
{
actualData[y] = y;
}
});
Assert.Equal(expectedData, actualData); Assert.Equal(expectedData, actualData);
} }
public static TheoryData<int, int, int, int, int, int, int> IterateRows_WithEffectiveMinimumPixelsLimit_Data = public static TheoryData<int, int, int, int, int, int, int> IterateRows_WithEffectiveMinimumPixelsLimit_Data =
new TheoryData<int, int, int, int, int, int, int> new TheoryData<int, int, int, int, int, int, int>
{ {
{ 2, 200, 50, 2, 1, -1, 2 }, { 2, 200, 50, 2, 1, -1, 2 },
{ 2, 200, 200, 1, 1, -1, 1 }, { 2, 200, 200, 1, 1, -1, 1 },
{ 4, 200, 100, 4, 2, 2, 2 }, { 4, 200, 100, 4, 2, 2, 2 },
{ 4, 300, 100, 8, 3, 3, 2 }, { 4, 300, 100, 8, 3, 3, 2 },
{ 2, 5000, 1, 4500, 1, -1, 4500 }, { 2, 5000, 1, 4500, 1, -1, 4500 },
{ 2, 5000, 1, 5000, 1, -1, 5000 }, { 2, 5000, 1, 5000, 1, -1, 5000 },
{ 2, 5000, 1, 5001, 2, 2501, 2500 }, { 2, 5000, 1, 5001, 2, 2501, 2500 },
}; };
[Theory] [Theory]
[MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))]
@ -217,20 +235,24 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int actualNumberOfSteps = 0; int actualNumberOfSteps = 0;
ParallelHelper.IterateRows( void RowAction(RowInterval rows)
rectangle, {
parallelSettings, Assert.True(rows.Min >= 0);
rows => Assert.True(rows.Max <= height);
{
Assert.True(rows.Min >= 0); int step = rows.Max - rows.Min;
Assert.True(rows.Max <= height); int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength;
Interlocked.Increment(ref actualNumberOfSteps);
Assert.Equal(expected, step);
}
int step = rows.Max - rows.Min; var operation = new TestRowIntervalOperation(RowAction);
int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength;
Interlocked.Increment(ref actualNumberOfSteps); ParallelRowIterator.IterateRows(
Assert.Equal(expected, step); rectangle,
}); in parallelSettings,
in operation);
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
} }
@ -254,33 +276,38 @@ namespace SixLabors.ImageSharp.Tests.Helpers
var rectangle = new Rectangle(0, 0, width, height); var rectangle = new Rectangle(0, 0, width, height);
int actualNumberOfSteps = 0; 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; void RowAction(RowInterval rows, Span<Vector4> buffer)
int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; {
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); Interlocked.Increment(ref actualNumberOfSteps);
Assert.Equal(expected, step); Assert.Equal(expected, step);
}); }
var operation = new TestRowIntervalOperation<Vector4>(RowAction);
ParallelRowIterator.IterateRows<TestRowIntervalOperation<Vector4>, Vector4>(
rectangle,
in parallelSettings,
in operation);
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
} }
public static readonly TheoryData<int, int, int, int, int, int, int> IterateRectangularBuffer_Data = public static readonly TheoryData<int, int, int, int, int, int, int> IterateRectangularBuffer_Data =
new TheoryData<int, int, int, int, int, int, int> new TheoryData<int, int, int, int, int, int, int>
{ {
{ 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox
{ 2, 582, 453, 10, 10, 291, 226 }, { 2, 582, 453, 10, 10, 291, 226 },
{ 16, 582, 453, 10, 10, 291, 226 }, { 16, 582, 453, 10, 10, 291, 226 },
{ 16, 582, 453, 10, 10, 1, 226 }, { 16, 582, 453, 10, 10, 1, 226 },
{ 16, 1, 453, 0, 10, 1, 226 }, { 16, 1, 453, 0, 10, 1, 226 },
}; };
[Theory] [Theory]
[MemberData(nameof(IterateRectangularBuffer_Data))] [MemberData(nameof(IterateRectangularBuffer_Data))]
@ -317,17 +344,21 @@ namespace SixLabors.ImageSharp.Tests.Helpers
// Fill actual data using IterateRows: // Fill actual data using IterateRows:
var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); 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, rect,
settings, settings,
rows => in operation);
{
this.output.WriteLine(rows.ToString());
for (int y = rows.Min; y < rows.Max; y++)
{
FillRow(y, actual);
}
});
// Assert: // Assert:
TestImageExtensions.CompareBuffers(expected.GetSingleSpan(), actual.GetSingleSpan()); TestImageExtensions.CompareBuffers(expected.GetSingleSpan(), actual.GetSingleSpan());
@ -345,8 +376,14 @@ namespace SixLabors.ImageSharp.Tests.Helpers
var rect = new Rectangle(0, 0, width, height); var rect = new Rectangle(0, 0, width, height);
void RowAction(RowInterval rows)
{
}
var operation = new TestRowIntervalOperation(RowAction);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>( 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); 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); 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>( 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); 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] [Fact]
public void CreateFrame_CustomFillColor() public void CreateFrame_CustomFillColor()
{ {
this.Image.Frames.CreateFrame(Rgba32.HotPink); this.Image.Frames.CreateFrame(Color.HotPink);
Assert.Equal(2, this.Image.Frames.Count); Assert.Equal(2, this.Image.Frames.Count);
this.Image.Frames[1].ComparePixelBufferTo(Rgba32.HotPink); this.Image.Frames[1].ComparePixelBufferTo(Color.HotPink);
} }
[Fact] [Fact]

8
tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests
} }
Rgba32[] expectedAllBlue = 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); Assert.Equal(2, this.Collection.Count);
var actualFrame = (ImageFrame<Rgba32>)this.Collection[1]; var actualFrame = (ImageFrame<Rgba32>)this.Collection[1];
@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests
} }
Rgba32[] expectedAllBlue = 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); Assert.Equal(2, this.Collection.Count);
var actualFrame = (ImageFrame<Rgba32>)this.Collection[0]; var actualFrame = (ImageFrame<Rgba32>)this.Collection[0];
@ -201,13 +201,13 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void CreateFrame_CustomFillColor() public void CreateFrame_CustomFillColor()
{ {
this.Image.Frames.CreateFrame(Rgba32.HotPink); this.Image.Frames.CreateFrame(Color.HotPink);
Assert.Equal(2, this.Image.Frames.Count); Assert.Equal(2, this.Image.Frames.Count);
var frame = (ImageFrame<Rgba32>)this.Image.Frames[1]; var frame = (ImageFrame<Rgba32>)this.Image.Frames[1];
frame.ComparePixelBufferTo(Rgba32.HotPink); frame.ComparePixelBufferTo(Color.HotPink);
} }
[Fact] [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. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -16,18 +16,18 @@ namespace SixLabors.ImageSharp.Tests
[InlineData(true)] [InlineData(true)]
public void FromPixels(bool useSpan) 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 using (Image<Rgba32> img = useSpan
? Image.LoadPixelData<Rgba32>(data.AsSpan(), 2, 2) ? Image.LoadPixelData<Rgba32>(data.AsSpan(), 2, 2)
: Image.LoadPixelData<Rgba32>(data, 2, 2)) : Image.LoadPixelData<Rgba32>(data, 2, 2))
{ {
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(Rgba32.Black, img[0, 0]); Assert.Equal(Color.Black, (Color)img[0, 0]);
Assert.Equal(Rgba32.White, img[0, 1]); Assert.Equal(Color.White, (Color)img[0, 1]);
Assert.Equal(Rgba32.White, img[1, 0]); Assert.Equal(Color.White, (Color)img[1, 0]);
Assert.Equal(Rgba32.Black, img[1, 1]); Assert.Equal(Color.Black, (Color)img[1, 1]);
} }
} }
@ -48,13 +48,13 @@ namespace SixLabors.ImageSharp.Tests
: Image.LoadPixelData<Rgba32>(data, 2, 2)) : Image.LoadPixelData<Rgba32>(data, 2, 2))
{ {
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(Rgba32.Black, img[0, 0]); Assert.Equal(Color.Black, (Color)img[0, 0]);
Assert.Equal(Rgba32.White, img[0, 1]); Assert.Equal(Color.White, (Color)img[0, 1]);
Assert.Equal(Rgba32.White, img[1, 0]); Assert.Equal(Color.White, (Color)img[1, 0]);
Assert.Equal(Rgba32.Black, img[1, 1]); 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() public void Configuration_Width_Height_BackgroundColor()
{ {
Configuration configuration = Configuration.Default.Clone(); Configuration configuration = Configuration.Default.Clone();
Rgba32 color = Rgba32.Aquamarine; Rgba32 color = Color.Aquamarine;
using (var image = new Image<Rgba32>(configuration, 11, 23, color)) 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> 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 }, { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Normal, Color.MidnightBlue },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) },
}; };
[Theory] [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> 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) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Rgba32.MistyRose }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Color.MistyRose },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Rgba32.MistyRose }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Color.MistyRose },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Rgba32.MistyRose }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Color.MistyRose },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Rgba32.MistyRose }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Color.MistyRose },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Rgba32.MidnightBlue }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Color.MidnightBlue },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Rgba32.MidnightBlue }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Color.MidnightBlue },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Rgba32.MidnightBlue }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Color.MidnightBlue },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Rgba32.MidnightBlue }, { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Color.MidnightBlue },
}; };
[Theory] [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 color1 = new Rgba32(0, 0, 0);
var color2 = new Rgba32(0, 0, 0, 1F); var color2 = new Rgba32(0, 0, 0, 1F);
var color3 = Rgba32.FromHex("#000"); var color3 = Rgba32.ParseHex("#000");
var color4 = Rgba32.FromHex("#000F"); var color4 = Rgba32.ParseHex("#000F");
var color5 = Rgba32.FromHex("#000000"); var color5 = Rgba32.ParseHex("#000000");
var color6 = Rgba32.FromHex("#000000FF"); var color6 = Rgba32.ParseHex("#000000FF");
Assert.Equal(color1, color2); Assert.Equal(color1, color2);
Assert.Equal(color1, color3); Assert.Equal(color1, color3);
@ -41,9 +41,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
{ {
var color1 = new Rgba32(255, 0, 0, 255); var color1 = new Rgba32(255, 0, 0, 255);
var color2 = new Rgba32(0, 0, 0, 255); var color2 = new Rgba32(0, 0, 0, 255);
var color3 = Rgba32.FromHex("#000"); var color3 = Rgba32.ParseHex("#000");
var color4 = Rgba32.FromHex("#000000"); var color4 = Rgba32.ParseHex("#000000");
var color5 = Rgba32.FromHex("#FF000000"); var color5 = Rgba32.ParseHex("#FF000000");
Assert.NotEqual(color1, color2); Assert.NotEqual(color1, color2);
Assert.NotEqual(color1, color3); Assert.NotEqual(color1, color3);
@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
public void FromAndToHex() public void FromAndToHex()
{ {
// 8 digit hex matches css4 spec. RRGGBBAA // 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(170, color.R);
Assert.Equal(187, color.G); Assert.Equal(187, color.G);
Assert.Equal(204, color.B); Assert.Equal(204, color.B);

2
tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs

@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Colors
[Fact] [Fact]
public void Color_Types_From_Hex_Produce_Equal_Scaled_Component_OutPut() 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"); var colorVector = RgbaVector.FromHex("183060C0");
Assert.Equal(color.R, (byte)(colorVector.R * 255)); 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] [Fact]
public void Glow_Color_GlowProcessorWithDefaultValues() public void Glow_Color_GlowProcessorWithDefaultValues()
{ {
this.operations.Glow(Rgba32.Aquamarine); this.operations.Glow(Color.Aquamarine);
GlowProcessor p = this.Verify<GlowProcessor>(); GlowProcessor p = this.Verify<GlowProcessor>();
Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); 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. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
{ {
public class PaletteQuantizerTests 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] [Fact]
public void PaletteQuantizerConstructor() public void PaletteQuantizerConstructor()

2
tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs

@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
} }
[Theory] [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) public void Transform_WithTaperMatrix<TPixel>(TestImageProvider<TPixel> provider, TaperSide taperSide, TaperCorner taperCorner)
where TPixel : struct, IPixel<TPixel> 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; Configuration config = Configuration.Default;
var quantizer = new WuQuantizer(false); 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])) using (IQuantizedFrame<Rgba32> result = quantizer.CreateFrameQuantizer<Rgba32>(config).QuantizeFrame(image.Frames[0]))
{ {
Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.GetPixelSpan().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]); 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 bottom = pixels.Height;
int height = (int)Math.Ceiling(pixels.Height / 6f); 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 red = Color.Red.ToPixel<TPixel>().ToVector4(); // use real color so we can see how 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 green = Color.Green.ToPixel<TPixel>().ToVector4(); // use real color so we can see how 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 blue = Color.Blue.ToPixel<TPixel>().ToVector4(); // use real color so we can see how it translates in the test pattern
var c = default(TPixel); var c = default(TPixel);

53
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -7,7 +7,6 @@ using System.IO;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -711,25 +710,43 @@ namespace SixLabors.ImageSharp.Tests
{ {
Rectangle sourceRectangle = this.SourceRectangle; Rectangle sourceRectangle = this.SourceRectangle;
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
sourceRectangle, var operation = new RowOperation(configuration, sourceRectangle, source);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
configuration, 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; ref Vector4 v = ref span[i];
for (int y = rows.Min; y < rows.Max; y++) v.W = 1F;
{ }
Span<TPixel> rowSpan = source.GetPixelRowSpan(y).Slice(sourceRectangle.Left, sourceRectangle.Width);
PixelOperations<TPixel>.Instance.ToVector4(configuration, rowSpan, tempSpan, PixelConversionModifiers.Scale); PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, span, rowSpan, 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);
}
});
} }
} }
} }

Loading…
Cancel
Save