diff --git a/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs
new file mode 100644
index 000000000..c02b3a00d
--- /dev/null
+++ b/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Processing.Processors.Transforms;
+
+namespace SixLabors.ImageSharp.Processing.Extensions.Transforms
+{
+ ///
+ /// Defines extensions that allow the application of swizzle operations on an
+ ///
+ public static class SwizzleExtensions
+ {
+ ///
+ /// Swizzles an image.
+ ///
+ /// The image to swizzle.
+ /// The swizzler function.
+ /// The swizzler function type.
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Swizzle(this IImageProcessingContext source, TSwizzler swizzler)
+ where TSwizzler : struct, ISwizzler
+ => source.ApplyProcessor(new SwizzleProcessor(swizzler));
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
index 8a4c703e0..352960f41 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
@@ -1,6 +1,11 @@
// Copyright (c) Six Labors.
// 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.Convolution
@@ -77,5 +82,56 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel
=> new BokehBlurProcessor(configuration, this, source, sourceRectangle);
+
+ ///
+ /// A implementing the horizontal convolution logic for .
+ ///
+ ///
+ /// This type is located in the non-generic class and not in , where
+ /// it is actually used, because it does not use any generic parameters internally. Defining in a non-generic class means that there will only
+ /// ever be a single instantiation of this type for the JIT/AOT compilers to process, instead of having duplicate versions for each pixel type.
+ ///
+ internal readonly struct ApplyHorizontalConvolutionRowOperation : IRowOperation
+ {
+ private readonly Rectangle bounds;
+ private readonly Buffer2D targetValues;
+ private readonly Buffer2D sourceValues;
+ private readonly Complex64[] kernel;
+ private readonly float z;
+ private readonly float w;
+ private readonly int maxY;
+ private readonly int maxX;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public ApplyHorizontalConvolutionRowOperation(
+ Rectangle bounds,
+ Buffer2D targetValues,
+ Buffer2D sourceValues,
+ Complex64[] kernel,
+ float z,
+ float w)
+ {
+ this.bounds = bounds;
+ this.maxY = this.bounds.Bottom - 1;
+ this.maxX = this.bounds.Right - 1;
+ this.targetValues = targetValues;
+ this.sourceValues = sourceValues;
+ this.kernel = kernel;
+ this.z = z;
+ this.w = w;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(int y)
+ {
+ Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X);
+
+ for (int x = 0; x < this.bounds.Width; x++)
+ {
+ Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w);
+ }
+ }
+ }
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
index cf5367343..dfe54bf2e 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
@@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
in verticalOperation);
// Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer
- var horizontalOperation = new ApplyHorizontalConvolutionRowOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W);
+ var horizontalOperation = new BokehBlurProcessor.ApplyHorizontalConvolutionRowOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W);
ParallelRowIterator.IterateRows(
configuration,
sourceRectangle,
@@ -175,52 +175,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
}
- ///
- /// A implementing the horizontal convolution logic for .
- ///
- private readonly struct ApplyHorizontalConvolutionRowOperation : IRowOperation
- {
- private readonly Rectangle bounds;
- private readonly Buffer2D targetValues;
- private readonly Buffer2D sourceValues;
- private readonly Complex64[] kernel;
- private readonly float z;
- private readonly float w;
- private readonly int maxY;
- private readonly int maxX;
-
- [MethodImpl(InliningOptions.ShortMethod)]
- public ApplyHorizontalConvolutionRowOperation(
- Rectangle bounds,
- Buffer2D targetValues,
- Buffer2D sourceValues,
- Complex64[] kernel,
- float z,
- float w)
- {
- this.bounds = bounds;
- this.maxY = this.bounds.Bottom - 1;
- this.maxX = this.bounds.Right - 1;
- this.targetValues = targetValues;
- this.sourceValues = sourceValues;
- this.kernel = kernel;
- this.z = z;
- this.w = w;
- }
-
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void Invoke(int y)
- {
- Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X);
-
- for (int x = 0; x < this.bounds.Width; x++)
- {
- Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w);
- }
- }
- }
-
///
/// A implementing the gamma exposure logic for .
///
@@ -304,7 +258,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int x = 0; x < this.bounds.Width; x++)
{
ref Vector4 v = ref Unsafe.Add(ref sourceRef, x);
- var clamp = Numerics.Clamp(v, low, high);
+ Vector4 clamp = Numerics.Clamp(v, low, high);
v.X = MathF.Pow(clamp.X, this.inverseGamma);
v.Y = MathF.Pow(clamp.Y, this.inverseGamma);
v.Z = MathF.Pow(clamp.Z, this.inverseGamma);
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs b/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs
new file mode 100644
index 000000000..efa3e35a4
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Encapsulate an algorithm to swizzle pixels in an image.
+ ///
+ public interface ISwizzler
+ {
+ ///
+ /// Gets the size of the image after transformation.
+ ///
+ Size DestinationSize { get; }
+
+ ///
+ /// Applies the swizzle transformation to a given point.
+ ///
+ /// Point to transform.
+ /// The transformed point.
+ Point Transform(Point point);
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs
new file mode 100644
index 000000000..aab17d292
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ internal class SwizzleProcessor : TransformProcessor
+ where TSwizzler : struct, ISwizzler
+ where TPixel : unmanaged, IPixel
+ {
+ private readonly TSwizzler swizzler;
+ private readonly Size destinationSize;
+
+ public SwizzleProcessor(Configuration configuration, TSwizzler swizzler, Image source, Rectangle sourceRectangle)
+ : base(configuration, source, sourceRectangle)
+ {
+ this.swizzler = swizzler;
+ this.destinationSize = swizzler.DestinationSize;
+ }
+
+ protected override Size GetDestinationSize()
+ => this.destinationSize;
+
+ protected override void OnFrameApply(ImageFrame source, ImageFrame destination)
+ {
+ Point p = default;
+ Point newPoint;
+ for (p.Y = 0; p.Y < source.Height; p.Y++)
+ {
+ Span rowSpan = source.GetPixelRowSpan(p.Y);
+ for (p.X = 0; p.X < source.Width; p.X++)
+ {
+ newPoint = this.swizzler.Transform(p);
+ destination[newPoint.X, newPoint.Y] = rowSpan[p.X];
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs
new file mode 100644
index 000000000..d48257334
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Defines a swizzle operation on an image.
+ ///
+ /// The swizzle function type.
+ public sealed class SwizzleProcessor : IImageProcessor
+ where TSwizzler : struct, ISwizzler
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The swizzler operation.
+ public SwizzleProcessor(TSwizzler swizzler)
+ {
+ this.Swizzler = swizzler;
+ }
+
+ ///
+ /// Gets the swizzler operation.
+ ///
+ public TSwizzler Swizzler { get; }
+
+ ///
+ public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle)
+ where TPixel : unmanaged, IPixel
+ => new SwizzleProcessor(configuration, this.Swizzler, source, sourceRectangle);
+ }
+}
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs
new file mode 100644
index 000000000..f508744fa
--- /dev/null
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Extensions.Transforms;
+using SixLabors.ImageSharp.Processing.Processors.Transforms;
+using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
+{
+ [GroupOutput("Transforms")]
+ public class SwizzleTests
+ {
+ private struct InvertXAndYSwizzler : ISwizzler
+ {
+ public InvertXAndYSwizzler(Size sourceSize)
+ {
+ this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width);
+ }
+
+ public Size DestinationSize { get; }
+
+ public Point Transform(Point point)
+ => new Point(point.Y, point.X);
+ }
+
+ [Theory]
+ [WithTestPatternImages(20, 37, PixelTypes.Rgba32)]
+ [WithTestPatternImages(53, 37, PixelTypes.Byte4)]
+ [WithTestPatternImages(17, 32, PixelTypes.Rgba32)]
+ public void InvertXAndYSwizzle(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image expectedImage = provider.GetImage();
+ using Image image = provider.GetImage();
+
+ image.Mutate(ctx => ctx.Swizzle(new InvertXAndYSwizzler(new Size(image.Width, image.Height))));
+
+ image.DebugSave(
+ provider,
+ nameof(InvertXAndYSwizzler),
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: true);
+
+ image.Mutate(ctx => ctx.Swizzle(new InvertXAndYSwizzler(new Size(image.Width, image.Height))));
+
+ image.DebugSave(
+ provider,
+ "Unswizzle",
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: true);
+
+ ImageComparer.Exact.VerifySimilarity(expectedImage, image);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs
new file mode 100644
index 000000000..cde6aeca3
--- /dev/null
+++ b/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Processing.Extensions.Transforms;
+using SixLabors.ImageSharp.Processing.Processors.Transforms;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Processing.Transforms
+{
+ public class SwizzleTests : BaseImageOperationsExtensionTest
+ {
+ private struct InvertXAndYSwizzler : ISwizzler
+ {
+ public InvertXAndYSwizzler(Size sourceSize)
+ {
+ this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width);
+ }
+
+ public Size DestinationSize { get; }
+
+ public Point Transform(Point point)
+ => new Point(point.Y, point.X);
+ }
+
+ [Fact]
+ public void InvertXAndYSwizzlerSetsCorrectSizes()
+ {
+ int width = 5;
+ int height = 10;
+
+ this.operations.Swizzle(new InvertXAndYSwizzler(new Size(width, height)));
+ SwizzleProcessor processor = this.Verify>();
+
+ Assert.Equal(processor.Swizzler.DestinationSize.Width, height);
+ Assert.Equal(processor.Swizzler.DestinationSize.Height, width);
+
+ this.operations.Swizzle(new InvertXAndYSwizzler(processor.Swizzler.DestinationSize));
+ SwizzleProcessor processor2 = this.Verify>(1);
+
+ Assert.Equal(processor2.Swizzler.DestinationSize.Width, width);
+ Assert.Equal(processor2.Swizzler.DestinationSize.Height, height);
+ }
+ }
+}