Skip to content

Commit 30e4c90

Browse files
authored
Merge branch 'master' into ci-cache-action
2 parents 2652977 + cad52a2 commit 30e4c90

File tree

8 files changed

+282
-48
lines changed

8 files changed

+282
-48
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using SixLabors.ImageSharp.Processing.Processors.Transforms;
5+
6+
namespace SixLabors.ImageSharp.Processing.Extensions.Transforms
7+
{
8+
/// <summary>
9+
/// Defines extensions that allow the application of swizzle operations on an <see cref="Image"/>
10+
/// </summary>
11+
public static class SwizzleExtensions
12+
{
13+
/// <summary>
14+
/// Swizzles an image.
15+
/// </summary>
16+
/// <param name="source">The image to swizzle.</param>
17+
/// <param name="swizzler">The swizzler function.</param>
18+
/// <typeparam name="TSwizzler">The swizzler function type.</typeparam>
19+
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
20+
public static IImageProcessingContext Swizzle<TSwizzler>(this IImageProcessingContext source, TSwizzler swizzler)
21+
where TSwizzler : struct, ISwizzler
22+
=> source.ApplyProcessor(new SwizzleProcessor<TSwizzler>(swizzler));
23+
}
24+
}

src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using System;
5+
using System.Numerics;
6+
using System.Runtime.CompilerServices;
7+
using SixLabors.ImageSharp.Advanced;
8+
using SixLabors.ImageSharp.Memory;
49
using SixLabors.ImageSharp.PixelFormats;
510

611
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
@@ -77,5 +82,56 @@ public BokehBlurProcessor(int radius, int components, float gamma)
7782
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
7883
where TPixel : unmanaged, IPixel<TPixel>
7984
=> new BokehBlurProcessor<TPixel>(configuration, this, source, sourceRectangle);
85+
86+
/// <summary>
87+
/// A <see langword="struct"/> implementing the horizontal convolution logic for <see cref="BokehBlurProcessor{T}"/>.
88+
/// </summary>
89+
/// <remarks>
90+
/// This type is located in the non-generic <see cref="BokehBlurProcessor"/> class and not in <see cref="BokehBlurProcessor{TPixel}"/>, where
91+
/// it is actually used, because it does not use any generic parameters internally. Defining in a non-generic class means that there will only
92+
/// ever be a single instantiation of this type for the JIT/AOT compilers to process, instead of having duplicate versions for each pixel type.
93+
/// </remarks>
94+
internal readonly struct ApplyHorizontalConvolutionRowOperation : IRowOperation
95+
{
96+
private readonly Rectangle bounds;
97+
private readonly Buffer2D<Vector4> targetValues;
98+
private readonly Buffer2D<ComplexVector4> sourceValues;
99+
private readonly Complex64[] kernel;
100+
private readonly float z;
101+
private readonly float w;
102+
private readonly int maxY;
103+
private readonly int maxX;
104+
105+
[MethodImpl(InliningOptions.ShortMethod)]
106+
public ApplyHorizontalConvolutionRowOperation(
107+
Rectangle bounds,
108+
Buffer2D<Vector4> targetValues,
109+
Buffer2D<ComplexVector4> sourceValues,
110+
Complex64[] kernel,
111+
float z,
112+
float w)
113+
{
114+
this.bounds = bounds;
115+
this.maxY = this.bounds.Bottom - 1;
116+
this.maxX = this.bounds.Right - 1;
117+
this.targetValues = targetValues;
118+
this.sourceValues = sourceValues;
119+
this.kernel = kernel;
120+
this.z = z;
121+
this.w = w;
122+
}
123+
124+
/// <inheritdoc/>
125+
[MethodImpl(InliningOptions.ShortMethod)]
126+
public void Invoke(int y)
127+
{
128+
Span<Vector4> targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X);
129+
130+
for (int x = 0; x < this.bounds.Width; x++)
131+
{
132+
Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w);
133+
}
134+
}
135+
}
80136
}
81137
}

src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ private void OnFrameApplyCore(
127127
in verticalOperation);
128128

129129
// Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer
130-
var horizontalOperation = new ApplyHorizontalConvolutionRowOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W);
130+
var horizontalOperation = new BokehBlurProcessor.ApplyHorizontalConvolutionRowOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W);
131131
ParallelRowIterator.IterateRows(
132132
configuration,
133133
sourceRectangle,
@@ -175,52 +175,6 @@ public void Invoke(int y)
175175
}
176176
}
177177

178-
/// <summary>
179-
/// A <see langword="struct"/> implementing the horizontal convolution logic for <see cref="BokehBlurProcessor{T}"/>.
180-
/// </summary>
181-
private readonly struct ApplyHorizontalConvolutionRowOperation : IRowOperation
182-
{
183-
private readonly Rectangle bounds;
184-
private readonly Buffer2D<Vector4> targetValues;
185-
private readonly Buffer2D<ComplexVector4> sourceValues;
186-
private readonly Complex64[] kernel;
187-
private readonly float z;
188-
private readonly float w;
189-
private readonly int maxY;
190-
private readonly int maxX;
191-
192-
[MethodImpl(InliningOptions.ShortMethod)]
193-
public ApplyHorizontalConvolutionRowOperation(
194-
Rectangle bounds,
195-
Buffer2D<Vector4> targetValues,
196-
Buffer2D<ComplexVector4> sourceValues,
197-
Complex64[] kernel,
198-
float z,
199-
float w)
200-
{
201-
this.bounds = bounds;
202-
this.maxY = this.bounds.Bottom - 1;
203-
this.maxX = this.bounds.Right - 1;
204-
this.targetValues = targetValues;
205-
this.sourceValues = sourceValues;
206-
this.kernel = kernel;
207-
this.z = z;
208-
this.w = w;
209-
}
210-
211-
/// <inheritdoc/>
212-
[MethodImpl(InliningOptions.ShortMethod)]
213-
public void Invoke(int y)
214-
{
215-
Span<Vector4> targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X);
216-
217-
for (int x = 0; x < this.bounds.Width; x++)
218-
{
219-
Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w);
220-
}
221-
}
222-
}
223-
224178
/// <summary>
225179
/// A <see langword="struct"/> implementing the gamma exposure logic for <see cref="BokehBlurProcessor{T}"/>.
226180
/// </summary>
@@ -304,7 +258,7 @@ public void Invoke(int y)
304258
for (int x = 0; x < this.bounds.Width; x++)
305259
{
306260
ref Vector4 v = ref Unsafe.Add(ref sourceRef, x);
307-
var clamp = Numerics.Clamp(v, low, high);
261+
Vector4 clamp = Numerics.Clamp(v, low, high);
308262
v.X = MathF.Pow(clamp.X, this.inverseGamma);
309263
v.Y = MathF.Pow(clamp.Y, this.inverseGamma);
310264
v.Z = MathF.Pow(clamp.Z, this.inverseGamma);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
5+
{
6+
/// <summary>
7+
/// Encapsulate an algorithm to swizzle pixels in an image.
8+
/// </summary>
9+
public interface ISwizzler
10+
{
11+
/// <summary>
12+
/// Gets the size of the image after transformation.
13+
/// </summary>
14+
Size DestinationSize { get; }
15+
16+
/// <summary>
17+
/// Applies the swizzle transformation to a given point.
18+
/// </summary>
19+
/// <param name="point">Point to transform.</param>
20+
/// <returns>The transformed point.</returns>
21+
Point Transform(Point point);
22+
}
23+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using SixLabors.ImageSharp.PixelFormats;
6+
7+
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
8+
{
9+
internal class SwizzleProcessor<TSwizzler, TPixel> : TransformProcessor<TPixel>
10+
where TSwizzler : struct, ISwizzler
11+
where TPixel : unmanaged, IPixel<TPixel>
12+
{
13+
private readonly TSwizzler swizzler;
14+
private readonly Size destinationSize;
15+
16+
public SwizzleProcessor(Configuration configuration, TSwizzler swizzler, Image<TPixel> source, Rectangle sourceRectangle)
17+
: base(configuration, source, sourceRectangle)
18+
{
19+
this.swizzler = swizzler;
20+
this.destinationSize = swizzler.DestinationSize;
21+
}
22+
23+
protected override Size GetDestinationSize()
24+
=> this.destinationSize;
25+
26+
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
27+
{
28+
Point p = default;
29+
Point newPoint;
30+
for (p.Y = 0; p.Y < source.Height; p.Y++)
31+
{
32+
Span<TPixel> rowSpan = source.GetPixelRowSpan(p.Y);
33+
for (p.X = 0; p.X < source.Width; p.X++)
34+
{
35+
newPoint = this.swizzler.Transform(p);
36+
destination[newPoint.X, newPoint.Y] = rowSpan[p.X];
37+
}
38+
}
39+
}
40+
}
41+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using SixLabors.ImageSharp.PixelFormats;
5+
6+
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
7+
{
8+
/// <summary>
9+
/// Defines a swizzle operation on an image.
10+
/// </summary>
11+
/// <typeparam name="TSwizzler">The swizzle function type.</typeparam>
12+
public sealed class SwizzleProcessor<TSwizzler> : IImageProcessor
13+
where TSwizzler : struct, ISwizzler
14+
{
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="SwizzleProcessor{TSwizzler}"/> class.
17+
/// </summary>
18+
/// <param name="swizzler">The swizzler operation.</param>
19+
public SwizzleProcessor(TSwizzler swizzler)
20+
{
21+
this.Swizzler = swizzler;
22+
}
23+
24+
/// <summary>
25+
/// Gets the swizzler operation.
26+
/// </summary>
27+
public TSwizzler Swizzler { get; }
28+
29+
/// <inheritdoc />
30+
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
31+
where TPixel : unmanaged, IPixel<TPixel>
32+
=> new SwizzleProcessor<TSwizzler, TPixel>(configuration, this.Swizzler, source, sourceRectangle);
33+
}
34+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using SixLabors.ImageSharp.PixelFormats;
5+
using SixLabors.ImageSharp.Processing;
6+
using SixLabors.ImageSharp.Processing.Extensions.Transforms;
7+
using SixLabors.ImageSharp.Processing.Processors.Transforms;
8+
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
9+
using Xunit;
10+
11+
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
12+
{
13+
[GroupOutput("Transforms")]
14+
public class SwizzleTests
15+
{
16+
private struct InvertXAndYSwizzler : ISwizzler
17+
{
18+
public InvertXAndYSwizzler(Size sourceSize)
19+
{
20+
this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width);
21+
}
22+
23+
public Size DestinationSize { get; }
24+
25+
public Point Transform(Point point)
26+
=> new Point(point.Y, point.X);
27+
}
28+
29+
[Theory]
30+
[WithTestPatternImages(20, 37, PixelTypes.Rgba32)]
31+
[WithTestPatternImages(53, 37, PixelTypes.Byte4)]
32+
[WithTestPatternImages(17, 32, PixelTypes.Rgba32)]
33+
public void InvertXAndYSwizzle<TPixel>(TestImageProvider<TPixel> provider)
34+
where TPixel : unmanaged, IPixel<TPixel>
35+
{
36+
using Image<TPixel> expectedImage = provider.GetImage();
37+
using Image<TPixel> image = provider.GetImage();
38+
39+
image.Mutate(ctx => ctx.Swizzle(new InvertXAndYSwizzler(new Size(image.Width, image.Height))));
40+
41+
image.DebugSave(
42+
provider,
43+
nameof(InvertXAndYSwizzler),
44+
appendPixelTypeToFileName: false,
45+
appendSourceFileOrDescription: true);
46+
47+
image.Mutate(ctx => ctx.Swizzle(new InvertXAndYSwizzler(new Size(image.Width, image.Height))));
48+
49+
image.DebugSave(
50+
provider,
51+
"Unswizzle",
52+
appendPixelTypeToFileName: false,
53+
appendSourceFileOrDescription: true);
54+
55+
ImageComparer.Exact.VerifySimilarity(expectedImage, image);
56+
}
57+
}
58+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using SixLabors.ImageSharp.Processing.Extensions.Transforms;
5+
using SixLabors.ImageSharp.Processing.Processors.Transforms;
6+
using Xunit;
7+
8+
namespace SixLabors.ImageSharp.Tests.Processing.Transforms
9+
{
10+
public class SwizzleTests : BaseImageOperationsExtensionTest
11+
{
12+
private struct InvertXAndYSwizzler : ISwizzler
13+
{
14+
public InvertXAndYSwizzler(Size sourceSize)
15+
{
16+
this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width);
17+
}
18+
19+
public Size DestinationSize { get; }
20+
21+
public Point Transform(Point point)
22+
=> new Point(point.Y, point.X);
23+
}
24+
25+
[Fact]
26+
public void InvertXAndYSwizzlerSetsCorrectSizes()
27+
{
28+
int width = 5;
29+
int height = 10;
30+
31+
this.operations.Swizzle(new InvertXAndYSwizzler(new Size(width, height)));
32+
SwizzleProcessor<InvertXAndYSwizzler> processor = this.Verify<SwizzleProcessor<InvertXAndYSwizzler>>();
33+
34+
Assert.Equal(processor.Swizzler.DestinationSize.Width, height);
35+
Assert.Equal(processor.Swizzler.DestinationSize.Height, width);
36+
37+
this.operations.Swizzle(new InvertXAndYSwizzler(processor.Swizzler.DestinationSize));
38+
SwizzleProcessor<InvertXAndYSwizzler> processor2 = this.Verify<SwizzleProcessor<InvertXAndYSwizzler>>(1);
39+
40+
Assert.Equal(processor2.Swizzler.DestinationSize.Width, width);
41+
Assert.Equal(processor2.Swizzler.DestinationSize.Height, height);
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)