diff --git a/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs new file mode 100644 index 0000000000..c02b3a00d6 --- /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/Transforms/ISwizzler.cs b/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs new file mode 100644 index 0000000000..efa3e35a4b --- /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 0000000000..aab17d2920 --- /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 0000000000..d48257334d --- /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 0000000000..f508744fae --- /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 0000000000..cde6aeca36 --- /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); + } + } +}