Skip to content

Commit

Permalink
Merge branch 'master' into ci-cache-action
Browse files Browse the repository at this point in the history
  • Loading branch information
antonfirsov authored Nov 28, 2020
2 parents 41ed93c + 2e72563 commit e338792
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Defines extensions that allow the application of swizzle operations on an <see cref="Image"/>
/// </summary>
public static class SwizzleExtensions
{
/// <summary>
/// Swizzles an image.
/// </summary>
/// <param name="source">The image to swizzle.</param>
/// <param name="swizzler">The swizzler function.</param>
/// <typeparam name="TSwizzler">The swizzler function type.</typeparam>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Swizzle<TSwizzler>(this IImageProcessingContext source, TSwizzler swizzler)
where TSwizzler : struct, ISwizzler
=> source.ApplyProcessor(new SwizzleProcessor<TSwizzler>(swizzler));
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -77,5 +82,56 @@ public BokehBlurProcessor(int radius, int components, float gamma)
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new BokehBlurProcessor<TPixel>(configuration, this, source, sourceRectangle);

/// <summary>
/// A <see langword="struct"/> implementing the horizontal convolution logic for <see cref="BokehBlurProcessor{T}"/>.
/// </summary>
/// <remarks>
/// This type is located in the non-generic <see cref="BokehBlurProcessor"/> class and not in <see cref="BokehBlurProcessor{TPixel}"/>, 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.
/// </remarks>
internal readonly struct ApplyHorizontalConvolutionRowOperation : IRowOperation
{
private readonly Rectangle bounds;
private readonly Buffer2D<Vector4> targetValues;
private readonly Buffer2D<ComplexVector4> sourceValues;
private readonly Complex64[] kernel;
private readonly float z;
private readonly float w;
private readonly int maxY;
private readonly int maxX;

[MethodImpl(InliningOptions.ShortMethod)]
public ApplyHorizontalConvolutionRowOperation(
Rectangle bounds,
Buffer2D<Vector4> targetValues,
Buffer2D<ComplexVector4> sourceValues,
Complex64[] kernel,
float z,
float w)
{
this.bounds = bounds;
this.maxY = this.bounds.Bottom - 1;
this.maxX = this.bounds.Right - 1;
this.targetValues = targetValues;
this.sourceValues = sourceValues;
this.kernel = kernel;
this.z = z;
this.w = w;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
{
Span<Vector4> 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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private void OnFrameApplyCore(
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,
Expand Down Expand Up @@ -175,52 +175,6 @@ public void Invoke(int y)
}
}

/// <summary>
/// A <see langword="struct"/> implementing the horizontal convolution logic for <see cref="BokehBlurProcessor{T}"/>.
/// </summary>
private readonly struct ApplyHorizontalConvolutionRowOperation : IRowOperation
{
private readonly Rectangle bounds;
private readonly Buffer2D<Vector4> targetValues;
private readonly Buffer2D<ComplexVector4> sourceValues;
private readonly Complex64[] kernel;
private readonly float z;
private readonly float w;
private readonly int maxY;
private readonly int maxX;

[MethodImpl(InliningOptions.ShortMethod)]
public ApplyHorizontalConvolutionRowOperation(
Rectangle bounds,
Buffer2D<Vector4> targetValues,
Buffer2D<ComplexVector4> sourceValues,
Complex64[] kernel,
float z,
float w)
{
this.bounds = bounds;
this.maxY = this.bounds.Bottom - 1;
this.maxX = this.bounds.Right - 1;
this.targetValues = targetValues;
this.sourceValues = sourceValues;
this.kernel = kernel;
this.z = z;
this.w = w;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
{
Span<Vector4> 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);
}
}
}

/// <summary>
/// A <see langword="struct"/> implementing the gamma exposure logic for <see cref="BokehBlurProcessor{T}"/>.
/// </summary>
Expand Down Expand Up @@ -304,7 +258,7 @@ public void Invoke(int y)
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);
Expand Down
23 changes: 23 additions & 0 deletions src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Encapsulate an algorithm to swizzle pixels in an image.
/// </summary>
public interface ISwizzler
{
/// <summary>
/// Gets the size of the image after transformation.
/// </summary>
Size DestinationSize { get; }

/// <summary>
/// Applies the swizzle transformation to a given point.
/// </summary>
/// <param name="point">Point to transform.</param>
/// <returns>The transformed point.</returns>
Point Transform(Point point);
}
}
Original file line number Diff line number Diff line change
@@ -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<TSwizzler, TPixel> : TransformProcessor<TPixel>
where TSwizzler : struct, ISwizzler
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly TSwizzler swizzler;
private readonly Size destinationSize;

public SwizzleProcessor(Configuration configuration, TSwizzler swizzler, Image<TPixel> 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<TPixel> source, ImageFrame<TPixel> destination)
{
Point p = default;
Point newPoint;
for (p.Y = 0; p.Y < source.Height; p.Y++)
{
Span<TPixel> 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];
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Defines a swizzle operation on an image.
/// </summary>
/// <typeparam name="TSwizzler">The swizzle function type.</typeparam>
public sealed class SwizzleProcessor<TSwizzler> : IImageProcessor
where TSwizzler : struct, ISwizzler
{
/// <summary>
/// Initializes a new instance of the <see cref="SwizzleProcessor{TSwizzler}"/> class.
/// </summary>
/// <param name="swizzler">The swizzler operation.</param>
public SwizzleProcessor(TSwizzler swizzler)
{
this.Swizzler = swizzler;
}

/// <summary>
/// Gets the swizzler operation.
/// </summary>
public TSwizzler Swizzler { get; }

/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new SwizzleProcessor<TSwizzler, TPixel>(configuration, this.Swizzler, source, sourceRectangle);
}
}
Original file line number Diff line number Diff line change
@@ -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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> expectedImage = provider.GetImage();
using Image<TPixel> 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);
}
}
}
44 changes: 44 additions & 0 deletions tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs
Original file line number Diff line number Diff line change
@@ -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<InvertXAndYSwizzler> processor = this.Verify<SwizzleProcessor<InvertXAndYSwizzler>>();

Assert.Equal(processor.Swizzler.DestinationSize.Width, height);
Assert.Equal(processor.Swizzler.DestinationSize.Height, width);

this.operations.Swizzle(new InvertXAndYSwizzler(processor.Swizzler.DestinationSize));
SwizzleProcessor<InvertXAndYSwizzler> processor2 = this.Verify<SwizzleProcessor<InvertXAndYSwizzler>>(1);

Assert.Equal(processor2.Swizzler.DestinationSize.Width, width);
Assert.Equal(processor2.Swizzler.DestinationSize.Height, height);
}
}
}

0 comments on commit e338792

Please sign in to comment.