From 0fbf85ff2f2459ad8b84f213a52debdf156dea36 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 5 May 2019 02:27:36 +0200 Subject: [PATCH] Finished refactoring transforms --- src/ImageSharp/Processing/CropExtensions.cs | 15 +- .../Processing/EntropyCropExtensions.cs | 19 +- src/ImageSharp/Processing/FlipExtensions.cs | 9 +- .../Effects/OilPaintingProcessor{TPixel}.cs | 2 +- .../Transforms/AffineTransformProcessor.cs | 106 +-------- .../AffineTransformProcessor{TPixel}.cs | 127 +++++++++++ .../Transforms/AutoOrientProcessor.cs | 92 +------- .../Transforms/AutoOrientProcessor{TPixel}.cs | 103 +++++++++ .../Processors/Transforms/CropProcessor.cs | 59 +---- .../Transforms/CropProcessor{TPixel}.cs | 75 ++++++ .../Transforms/EntropyCropProcessor.cs | 46 +--- .../EntropyCropProcessor{TPixel}.cs | 59 +++++ .../Processors/Transforms/FlipProcessor.cs | 76 +------ .../Transforms/FlipProcessor{TPixel}.cs | 87 +++++++ .../ProjectiveTransformProcessor.cs | 109 +-------- .../ProjectiveTransformProcessor{TPixel}.cs | 126 ++++++++++ .../Processors/Transforms/RotateProcessor.cs | 207 +---------------- .../Transforms/RotateProcessor{TPixel}.cs | 215 ++++++++++++++++++ .../Processors/Transforms/SkewProcessor.cs | 10 +- .../Transforms/TransformProcessorBase.cs | 2 +- .../Transforms/TransformProcessorHelpers.cs | 2 +- src/ImageSharp/Processing/RotateExtensions.cs | 24 +- .../Processing/RotateFlipExtensions.cs | 6 +- src/ImageSharp/Processing/SkewExtensions.cs | 24 +- .../Processing/TransformExtensions.cs | 97 ++++---- .../ImageSharp.Tests/Drawing/DrawImageTest.cs | 3 +- .../Processing/Transforms/CropTest.cs | 4 +- .../Processing/Transforms/EntropyCropTest.cs | 2 +- .../Processing/Transforms/FlipTests.cs | 2 +- .../Processing/Transforms/PadTest.cs | 8 +- .../Processing/Transforms/RotateFlipTests.cs | 4 +- .../Processing/Transforms/RotateTests.cs | 4 +- .../Processing/Transforms/SkewTest.cs | 2 +- .../Transforms/TransformsHelpersTest.cs | 2 +- 34 files changed, 953 insertions(+), 775 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs create mode 100644 src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs create mode 100644 src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs create mode 100644 src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs create mode 100644 src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs create mode 100644 src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs create mode 100644 src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs diff --git a/src/ImageSharp/Processing/CropExtensions.cs b/src/ImageSharp/Processing/CropExtensions.cs index 1c0d80afc..6aaff5656 100644 --- a/src/ImageSharp/Processing/CropExtensions.cs +++ b/src/ImageSharp/Processing/CropExtensions.cs @@ -1,40 +1,35 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of cropping operations to the type. + /// Adds extensions that allow the application of cropping operations to the type. /// public static class CropExtensions { /// /// Crops an image to the given width and height. /// - /// The pixel format. /// The image to resize. /// The target image width. /// The target image height. /// The - public static IImageProcessingContext Crop(this IImageProcessingContext source, int width, int height) - where TPixel : struct, IPixel - => Crop(source, new Rectangle(0, 0, width, height)); + public static IImageProcessingContext Crop(this IImageProcessingContext source, int width, int height) => + Crop(source, new Rectangle(0, 0, width, height)); /// /// Crops an image to the given rectangle. /// - /// The pixel format. /// The image to crop. /// /// The structure that specifies the portion of the image object to retain. /// /// The - public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) - where TPixel : struct, IPixel - => source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); + public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) => + source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/EntropyCropExtensions.cs b/src/ImageSharp/Processing/EntropyCropExtensions.cs index 157e69ef2..34bc5daeb 100644 --- a/src/ImageSharp/Processing/EntropyCropExtensions.cs +++ b/src/ImageSharp/Processing/EntropyCropExtensions.cs @@ -1,35 +1,30 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of entropy cropping operations to the type. + /// Adds extensions that allow the application of entropy cropping operations to the type. /// public static class EntropyCropExtensions { /// /// Crops an image to the area of greatest entropy using a threshold for entropic density of .5F. /// - /// The pixel format. /// The image to crop. - /// The - public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source) - where TPixel : struct, IPixel - => source.ApplyProcessor(new EntropyCropProcessor()); + /// The . + public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source) => + source.ApplyProcessor(new EntropyCropProcessor()); /// /// Crops an image to the area of greatest entropy. /// - /// The pixel format. /// The image to crop. /// The threshold for entropic density. - /// The - public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source, float threshold) - where TPixel : struct, IPixel - => source.ApplyProcessor(new EntropyCropProcessor(threshold)); + /// The . + public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source, float threshold) => + source.ApplyProcessor(new EntropyCropProcessor(threshold)); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/FlipExtensions.cs b/src/ImageSharp/Processing/FlipExtensions.cs index dfbff7e4d..bc862972e 100644 --- a/src/ImageSharp/Processing/FlipExtensions.cs +++ b/src/ImageSharp/Processing/FlipExtensions.cs @@ -1,25 +1,22 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of flipping operations to the type. + /// Adds extensions that allow the application of flipping operations to the type. /// public static class FlipExtensions { /// /// Flips an image by the given instructions. /// - /// The pixel format. /// The image to rotate, flip, or both. /// The to perform the flip. /// The - public static IImageProcessingContext Flip(this IImageProcessingContext source, FlipMode flipMode) - where TPixel : struct, IPixel - => source.ApplyProcessor(new FlipProcessor(flipMode)); + public static IImageProcessingContext Flip(this IImageProcessingContext source, FlipMode flipMode) + => source.ApplyProcessor(new FlipProcessor(flipMode)); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 9775e5ca9..7efb71fa1 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -1,5 +1,5 @@ // 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; diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 7633ed441..713f04265 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -1,26 +1,20 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides the base methods to perform affine transforms on an image. + /// Defines an affine transformation applicable on an . /// - /// The pixel format. - internal class AffineTransformProcessor : TransformProcessorBase - where TPixel : struct, IPixel + public class AffineTransformProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The transform matrix. /// The sampler to perform the transform operation. @@ -48,95 +42,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public Size TargetDimensions { get; } - /// - protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + /// + public virtual IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = - source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions, x.Metadata.DeepClone())); - - // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); - } - - /// - protected override void OnFrameApply( - ImageFrame source, - ImageFrame destination, - Rectangle sourceRectangle, - Configuration configuration) - { - // Handle tranforms that result in output identical to the original. - if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix3x2.Identity)) - { - // The clone will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); - return; - } - - int width = this.TargetDimensions.Width; - var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions); - - // Convert from screen to world space. - Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 matrix); - - if (this.Sampler is NearestNeighborResampler) - { - ParallelHelper.IterateRows( - targetBounds, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destRow = destination.GetPixelRowSpan(y); - - for (int x = 0; x < width; x++) - { - var point = Point.Transform(new Point(x, y), matrix); - if (sourceRectangle.Contains(point.X, point.Y)) - { - destRow[x] = source[point.X, point.Y]; - } - } - } - }); - - return; - } - - var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.Sampler); - try - { - ParallelHelper.IterateRowsWithTempBuffer( - targetBounds, - configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = destination.GetPixelRowSpan(y); - PixelOperations.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.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); - } - }); - } - finally - { - kernel.Dispose(); - } + return new AffineTransformProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs new file mode 100644 index 000000000..e57ce826b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -0,0 +1,127 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides the base methods to perform affine transforms on an image. + /// + /// The pixel format. + internal class AffineTransformProcessor : TransformProcessorBase + where TPixel : struct, IPixel + { + public AffineTransformProcessor(AffineTransformProcessor definition) + { + this.Definition = definition; + } + + private Size TargetDimensions => this.Definition.TargetDimensions; + + private Matrix3x2 TransformMatrix => this.Definition.TransformMatrix; + + protected AffineTransformProcessor Definition { get; } + + /// + protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + { + // We will always be creating the clone even for mutate because we may need to resize the canvas + IEnumerable> frames = + source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions, x.Metadata.DeepClone())); + + // Use the overload to prevent an extra frame being added + return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); + } + + /// + protected override void OnFrameApply( + ImageFrame source, + ImageFrame destination, + Rectangle sourceRectangle, + Configuration configuration) + { + // Handle tranforms that result in output identical to the original. + if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix3x2.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return; + } + + int width = this.TargetDimensions.Width; + var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions); + + // Convert from screen to world space. + Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 matrix); + + var sampler = this.Definition.Sampler; + + if (sampler is NearestNeighborResampler) + { + ParallelHelper.IterateRows( + targetBounds, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span destRow = destination.GetPixelRowSpan(y); + + for (int x = 0; x < width; x++) + { + var point = Point.Transform(new Point(x, y), matrix); + if (sourceRectangle.Contains(point.X, point.Y)) + { + destRow[x] = source[point.X, point.Y]; + } + } + } + }); + + return; + } + + var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), sampler); + try + { + ParallelHelper.IterateRowsWithTempBuffer( + targetBounds, + configuration, + (rows, vectorBuffer) => + { + Span vectorSpan = vectorBuffer.Span; + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = destination.GetPixelRowSpan(y); + PixelOperations.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.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); + } + }); + } + finally + { + kernel.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs index 5b9e3dde2..9bbbba843 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs @@ -1,103 +1,21 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. /// - /// The pixel format. - internal class AutoOrientProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class AutoOrientProcessor : IImageProcessor { - /// - protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - OrientationMode orientation = GetExifOrientation(source); - Size size = sourceRectangle.Size; - switch (orientation) - { - case OrientationMode.TopRight: - new FlipProcessor(FlipMode.Horizontal).Apply(source, sourceRectangle); - break; - - case OrientationMode.BottomRight: - new RotateProcessor((int)RotateMode.Rotate180, size).Apply(source, sourceRectangle); - break; - - case OrientationMode.BottomLeft: - new FlipProcessor(FlipMode.Vertical).Apply(source, sourceRectangle); - break; - - case OrientationMode.LeftTop: - new RotateProcessor((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle); - new FlipProcessor(FlipMode.Horizontal).Apply(source, sourceRectangle); - break; - - case OrientationMode.RightTop: - new RotateProcessor((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle); - break; - - case OrientationMode.RightBottom: - new FlipProcessor(FlipMode.Vertical).Apply(source, sourceRectangle); - new RotateProcessor((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle); - break; - - case OrientationMode.LeftBottom: - new RotateProcessor((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle); - break; - - case OrientationMode.Unknown: - case OrientationMode.TopLeft: - default: - break; - } - } - - /// - protected override void OnFrameApply(ImageFrame sourceBase, Rectangle sourceRectangle, Configuration config) - { - // All processing happens at the image level within BeforeImageApply(); - } - - /// - /// Returns the current EXIF orientation - /// - /// The image to auto rotate. - /// The - private static OrientationMode GetExifOrientation(Image source) - { - if (source.Metadata.ExifProfile is null) - { - return OrientationMode.Unknown; - } - - ExifValue value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation); - if (value is null) - { - return OrientationMode.Unknown; - } - - OrientationMode orientation; - if (value.DataType == ExifDataType.Short) - { - orientation = (OrientationMode)value.Value; - } - else - { - orientation = (OrientationMode)Convert.ToUInt16(value.Value); - source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation); - } - - source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)OrientationMode.TopLeft); - - return orientation; + return new AutoOrientProcessor(); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs new file mode 100644 index 000000000..257c223dc --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. + /// + /// The pixel format. + internal class AutoOrientProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) + { + OrientationMode orientation = GetExifOrientation(source); + Size size = sourceRectangle.Size; + switch (orientation) + { + case OrientationMode.TopRight: + new FlipProcessor(FlipMode.Horizontal).Apply(source, sourceRectangle); + break; + + case OrientationMode.BottomRight: + new RotateProcessor((int)RotateMode.Rotate180, size).Apply(source, sourceRectangle); + break; + + case OrientationMode.BottomLeft: + new FlipProcessor(FlipMode.Vertical).Apply(source, sourceRectangle); + break; + + case OrientationMode.LeftTop: + new RotateProcessor((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle); + new FlipProcessor(FlipMode.Horizontal).Apply(source, sourceRectangle); + break; + + case OrientationMode.RightTop: + new RotateProcessor((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle); + break; + + case OrientationMode.RightBottom: + new FlipProcessor(FlipMode.Vertical).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle); + break; + + case OrientationMode.LeftBottom: + new RotateProcessor((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle); + break; + + case OrientationMode.Unknown: + case OrientationMode.TopLeft: + default: + break; + } + } + + /// + protected override void OnFrameApply(ImageFrame sourceBase, Rectangle sourceRectangle, Configuration config) + { + // All processing happens at the image level within BeforeImageApply(); + } + + /// + /// Returns the current EXIF orientation + /// + /// The image to auto rotate. + /// The + private static OrientationMode GetExifOrientation(Image source) + { + if (source.Metadata.ExifProfile is null) + { + return OrientationMode.Unknown; + } + + ExifValue value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation); + if (value is null) + { + return OrientationMode.Unknown; + } + + OrientationMode orientation; + if (value.DataType == ExifDataType.Short) + { + orientation = (OrientationMode)value.Value; + } + else + { + orientation = (OrientationMode)Convert.ToUInt16(value.Value); + source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation); + } + + source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)OrientationMode.TopLeft); + + return orientation; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index 5baa196a0..76f223e03 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -1,32 +1,28 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; -using System.Linq; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides methods to allow the cropping of an image. + /// Defines a crop operation on an image. /// - /// The pixel format. - internal class CropProcessor : TransformProcessorBase - where TPixel : struct, IPixel + public sealed class CropProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The target cropped rectangle. /// The source image size. public CropProcessor(Rectangle cropRectangle, Size sourceSize) { // Check bounds here and throw if we are passed a rectangle exceeding our source bounds. - Guard.IsTrue(new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), nameof(cropRectangle), "Crop rectangle should be smaller than the source bounds."); + Guard.IsTrue( + new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), + nameof(cropRectangle), + "Crop rectangle should be smaller than the source bounds."); this.CropRectangle = cropRectangle; } @@ -35,44 +31,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public Rectangle CropRectangle { get; } - /// - protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.Metadata.DeepClone())); - - // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); - } - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) - { - // Handle resize dimensions identical to the original - if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.CropRectangle) - { - // the cloned will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); - return; - } - - Rectangle rect = this.CropRectangle; - - // Copying is cheap, we should process more pixels per task: - ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); - - ParallelHelper.IterateRows( - rect, - parallelSettings, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y).Slice(rect.Left); - Span targetRow = destination.GetPixelRowSpan(y - rect.Top); - sourceRow.Slice(0, rect.Width).CopyTo(targetRow); - } - }); + return new CropProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs new file mode 100644 index 000000000..9bddda382 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides methods to allow the cropping of an image. + /// + /// The pixel format. + internal class CropProcessor : TransformProcessorBase + where TPixel : struct, IPixel + { + private readonly CropProcessor definition; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public CropProcessor(CropProcessor definition) + { + this.definition = definition; + } + + private Rectangle CropRectangle => this.definition.CropRectangle; + + /// + protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + { + // We will always be creating the clone even for mutate because we may need to resize the canvas + IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.Metadata.DeepClone())); + + // Use the overload to prevent an extra frame being added + return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); + } + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + // Handle resize dimensions identical to the original + if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.CropRectangle) + { + // the cloned will be blank here copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return; + } + + Rectangle rect = this.CropRectangle; + + // Copying is cheap, we should process more pixels per task: + ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); + + ParallelHelper.IterateRows( + rect, + parallelSettings, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y).Slice(rect.Left); + Span targetRow = destination.GetPixelRowSpan(y - rect.Top); + sourceRow.Slice(0, rect.Width).CopyTo(targetRow); + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs index 0e744dd96..dee5e7fb3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs @@ -1,31 +1,25 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Binarization; -using SixLabors.ImageSharp.Processing.Processors.Convolution; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides methods to allow the cropping of an image to preserve areas of highest entropy. + /// Defines cropping operation that preserves areas of highest entropy. /// - /// The pixel format. - internal class EntropyCropProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class EntropyCropProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public EntropyCropProcessor() - : this(.5F) + : this(.5F) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The threshold to split the image. Must be between 0 and 1. /// @@ -42,33 +36,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public float Threshold { get; } - /// - protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - Rectangle rectangle; - - // All frames have be the same size so we only need to calculate the correct dimensions for the first frame - using (ImageFrame temp = source.Frames.RootFrame.Clone()) - { - Configuration configuration = source.GetConfiguration(); - - // Detect the edges. - new SobelProcessor(false).ApplyToFrame(temp, sourceRectangle, configuration); - - // Apply threshold binarization filter. - new BinaryThresholdProcessor(this.Threshold).Apply(temp, sourceRectangle, configuration); - - // Search for the first white pixels - rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); - } - - new CropProcessor(rectangle, source.Size()).Apply(source, sourceRectangle); - } - - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - // All processing happens at the image level within BeforeImageApply(); + return new EntropyCropProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs new file mode 100644 index 000000000..eaeb6939e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Binarization; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides methods to allow the cropping of an image to preserve areas of highest entropy. + /// + /// The pixel format. + internal class EntropyCropProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private readonly EntropyCropProcessor definition; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public EntropyCropProcessor(EntropyCropProcessor definition) + { + this.definition = definition; + } + + /// + protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) + { + Rectangle rectangle; + + // All frames have be the same size so we only need to calculate the correct dimensions for the first frame + using (ImageFrame temp = source.Frames.RootFrame.Clone()) + { + Configuration configuration = source.GetConfiguration(); + + // Detect the edges. + new SobelProcessor(false).ApplyToFrame(temp, sourceRectangle, configuration); + + // Apply threshold binarization filter. + new BinaryThresholdProcessor(this.definition.Threshold).Apply(temp, sourceRectangle, configuration); + + // Search for the first white pixels + rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); + } + + new CropProcessor(rectangle, source.Size()).Apply(source, sourceRectangle); + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + // All processing happens at the image level within BeforeImageApply(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs index c6f5e9d7b..9a3eab6cc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs @@ -1,27 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Buffers; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides methods that allow the flipping of an image around its center point. + /// Defines a flipping around the center point of the image. /// - /// The pixel format. - internal class FlipProcessor : ImageProcessor - where TPixel : struct, IPixel + public class FlipProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The used to perform flipping. public FlipProcessor(FlipMode flipMode) @@ -34,63 +24,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public FlipMode FlipMode { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - switch (this.FlipMode) - { - // No default needed as we have already set the pixels. - case FlipMode.Vertical: - this.FlipX(source, configuration); - break; - case FlipMode.Horizontal: - this.FlipY(source, configuration); - break; - } - } - - /// - /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image. - /// - /// The source image to apply the process to. - /// The configuration. - private void FlipX(ImageFrame source, Configuration configuration) - { - int height = source.Height; - - using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width)) - { - Span temp = tempBuffer.Memory.Span; - - for (int yTop = 0; yTop < height / 2; yTop++) - { - int yBottom = height - yTop - 1; - Span topRow = source.GetPixelRowSpan(yBottom); - Span bottomRow = source.GetPixelRowSpan(yTop); - topRow.CopyTo(temp); - bottomRow.CopyTo(topRow); - temp.CopyTo(bottomRow); - } - } - } - - /// - /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. - /// - /// The source image to apply the process to. - /// The configuration. - private void FlipY(ImageFrame source, Configuration configuration) - { - ParallelHelper.IterateRows( - source.Bounds(), - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - source.GetPixelRowSpan(y).Reverse(); - } - }); + return new FlipProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs new file mode 100644 index 000000000..024786209 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs @@ -0,0 +1,87 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides methods that allow the flipping of an image around its center point. + /// + /// The pixel format. + internal class FlipProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private readonly FlipProcessor definition; + + public FlipProcessor(FlipProcessor definition) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + switch (this.definition.FlipMode) + { + // No default needed as we have already set the pixels. + case FlipMode.Vertical: + this.FlipX(source, configuration); + break; + case FlipMode.Horizontal: + this.FlipY(source, configuration); + break; + } + } + + /// + /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image. + /// + /// The source image to apply the process to. + /// The configuration. + private void FlipX(ImageFrame source, Configuration configuration) + { + int height = source.Height; + + using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width)) + { + Span temp = tempBuffer.Memory.Span; + + for (int yTop = 0; yTop < height / 2; yTop++) + { + int yBottom = height - yTop - 1; + Span topRow = source.GetPixelRowSpan(yBottom); + Span bottomRow = source.GetPixelRowSpan(yTop); + topRow.CopyTo(temp); + bottomRow.CopyTo(topRow); + temp.CopyTo(bottomRow); + } + } + } + + /// + /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. + /// + /// The source image to apply the process to. + /// The configuration. + private void FlipY(ImageFrame source, Configuration configuration) + { + ParallelHelper.IterateRows( + source.Bounds(), + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + source.GetPixelRowSpan(y).Reverse(); + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index 6c7271c5e..3a86b3fe4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -1,26 +1,20 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides the base methods to perform non-affine transforms on an image. + /// Defines a projective transformation applicable to an . /// - /// The pixel format. - internal class ProjectiveTransformProcessor : TransformProcessorBase - where TPixel : struct, IPixel + public sealed class ProjectiveTransformProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The transform matrix. /// The sampler to perform the transform operation. @@ -39,103 +33,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public IResampler Sampler { get; } /// - /// Gets the matrix used to supply the projective transform + /// Gets the matrix used to supply the projective transform. /// public Matrix4x4 TransformMatrix { get; } /// - /// Gets the target dimensions to constrain the transformed image to + /// Gets the target dimensions to constrain the transformed image to. /// public Size TargetDimensions { get; } - /// - protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = - source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.Metadata.DeepClone())); - - // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); - } - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) - { - // Handle tranforms that result in output identical to the original. - if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix4x4.Identity)) - { - // The clone will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); - return; - } - - int width = this.TargetDimensions.Width; - var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions); - - // Convert from screen to world space. - Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 matrix); - - if (this.Sampler is NearestNeighborResampler) - { - ParallelHelper.IterateRows( - targetBounds, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span 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 (sourceRectangle.Contains(px, py)) - { - destRow[x] = source[px, py]; - } - } - } - }); - - return; - } - - var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.Sampler); - try - { - ParallelHelper.IterateRowsWithTempBuffer( - targetBounds, - configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = destination.GetPixelRowSpan(y); - PixelOperations.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. - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); - kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan); - } - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); - } - }); - } - finally - { - kernel.Dispose(); - } + return new ProjectiveTransformProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs new file mode 100644 index 000000000..e6d885803 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -0,0 +1,126 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides the base methods to perform non-affine transforms on an image. + /// + /// The pixel format. + internal class ProjectiveTransformProcessor : TransformProcessorBase + where TPixel : struct, IPixel + { + private readonly ProjectiveTransformProcessor definition; + + public ProjectiveTransformProcessor(ProjectiveTransformProcessor definition) + { + this.definition = definition; + } + + private Size TargetDimensions => this.definition.TargetDimensions; + + /// + protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + { + // We will always be creating the clone even for mutate because we may need to resize the canvas + IEnumerable> frames = + source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.Metadata.DeepClone())); + + // Use the overload to prevent an extra frame being added + return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); + } + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + Matrix4x4 transformMatrix = this.definition.TransformMatrix; + + // Handle tranforms that result in output identical to the original. + if (transformMatrix.Equals(default) || transformMatrix.Equals(Matrix4x4.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return; + } + + int width = this.TargetDimensions.Width; + var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions); + + // Convert from screen to world space. + Matrix4x4.Invert(transformMatrix, out Matrix4x4 matrix); + + IResampler sampler = this.definition.Sampler; + + if (sampler is NearestNeighborResampler) + { + ParallelHelper.IterateRows( + targetBounds, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span 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 (sourceRectangle.Contains(px, py)) + { + destRow[x] = source[px, py]; + } + } + } + }); + + return; + } + + var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), sampler); + try + { + ParallelHelper.IterateRowsWithTempBuffer( + targetBounds, + configuration, + (rows, vectorBuffer) => + { + Span vectorSpan = vectorBuffer.Span; + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = destination.GetPixelRowSpan(y); + PixelOperations.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. + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); + kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan); + } + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); + } + }); + } + finally + { + kernel.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index 57902a5e6..ef0671d20 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -1,26 +1,19 @@ // 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.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides methods that allow the rotating of images. + /// Defines a rotation applicable to an . /// - /// The pixel format. - internal class RotateProcessor : AffineTransformProcessor - where TPixel : struct, IPixel + public sealed class RotateProcessor : AffineTransformProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The angle of rotation in degrees. /// The source image size @@ -30,16 +23,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The angle of rotation in degrees. /// The sampler to perform the rotating operation. /// The source image size public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) : this( - TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize), - sampler, - sourceSize) + TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize), + sampler, + sourceSize) => this.Degrees = degrees; // Helper constructor @@ -53,190 +46,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public float Degrees { get; } - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + /// + public override IImageProcessor CreatePixelSpecificProcessor() { - if (this.OptimizedApply(source, destination, configuration)) - { - return; - } - - base.OnFrameApply(source, destination, sourceRectangle, configuration); - } - - /// - protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) - { - ExifProfile profile = destination.Metadata.ExifProfile; - if (profile is null) - { - return; - } - - if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon) - { - // No need to do anything so return. - return; - } - - profile.RemoveValue(ExifTag.Orientation); - - base.AfterImageApply(source, destination, sourceRectangle); - } - - /// - /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range - /// - /// The angle of rotation in degrees. - /// The - private static float WrapDegrees(float degrees) - { - degrees %= 360; - - while (degrees < 0) - { - degrees += 360; - } - - return degrees; - } - - /// - /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. - /// - /// The source image. - /// The destination image. - /// The configuration. - /// - /// The - /// - private bool OptimizedApply(ImageFrame source, ImageFrame destination, Configuration configuration) - { - // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. - float degrees = WrapDegrees(this.Degrees); - - if (MathF.Abs(degrees) < Constants.Epsilon) - { - // The destination will be blank here so copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); - return true; - } - - if (MathF.Abs(degrees - 90) < Constants.Epsilon) - { - this.Rotate90(source, destination, configuration); - return true; - } - - if (MathF.Abs(degrees - 180) < Constants.Epsilon) - { - this.Rotate180(source, destination, configuration); - return true; - } - - if (MathF.Abs(degrees - 270) < Constants.Epsilon) - { - this.Rotate270(source, destination, configuration); - return true; - } - - return false; - } - - /// - /// Rotates the image 270 degrees clockwise at the centre point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) - { - int width = source.Width; - int height = source.Height; - Rectangle destinationBounds = destination.Bounds(); - - ParallelHelper.IterateRows( - source.Bounds(), - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span 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]; - } - } - } - }); - } - - /// - /// Rotates the image 180 degrees clockwise at the centre point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) - { - int width = source.Width; - int height = source.Height; - - ParallelHelper.IterateRows( - source.Bounds(), - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = destination.GetPixelRowSpan(height - y - 1); - - for (int x = 0; x < width; x++) - { - targetRow[width - x - 1] = sourceRow[x]; - } - } - }); - } - - /// - /// Rotates the image 90 degrees clockwise at the centre point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) - { - int width = source.Width; - int height = source.Height; - Rectangle destinationBounds = destination.Bounds(); - - ParallelHelper.IterateRows( - source.Bounds(), - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y); - int newX = height - y - 1; - for (int x = 0; x < width; x++) - { - if (destinationBounds.Contains(newX, x)) - { - destination[newX, x] = sourceRow[x]; - } - } - } - }); + return new RotateProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs new file mode 100644 index 000000000..aac6f6514 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -0,0 +1,215 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides methods that allow the rotating of images. + /// + /// The pixel format. + internal class RotateProcessor : AffineTransformProcessor + where TPixel : struct, IPixel + { + public RotateProcessor(RotateProcessor definition) + : base(definition) + { + this.Degrees = definition.Degrees; + } + + private float Degrees { get; } + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + if (this.OptimizedApply(source, destination, configuration)) + { + return; + } + + base.OnFrameApply(source, destination, sourceRectangle, configuration); + } + + /// + protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) + { + ExifProfile profile = destination.Metadata.ExifProfile; + if (profile is null) + { + return; + } + + if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon) + { + // No need to do anything so return. + return; + } + + profile.RemoveValue(ExifTag.Orientation); + + base.AfterImageApply(source, destination, sourceRectangle); + } + + /// + /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range + /// + /// The angle of rotation in degrees. + /// The . + private static float WrapDegrees(float degrees) + { + degrees %= 360; + + while (degrees < 0) + { + degrees += 360; + } + + return degrees; + } + + /// + /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. + /// + /// The source image. + /// The destination image. + /// The configuration. + /// + /// The + /// + private bool OptimizedApply(ImageFrame source, ImageFrame destination, Configuration configuration) + { + // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. + float degrees = WrapDegrees(this.Degrees); + + if (MathF.Abs(degrees) < Constants.Epsilon) + { + // The destination will be blank here so copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return true; + } + + if (MathF.Abs(degrees - 90) < Constants.Epsilon) + { + this.Rotate90(source, destination, configuration); + return true; + } + + if (MathF.Abs(degrees - 180) < Constants.Epsilon) + { + this.Rotate180(source, destination, configuration); + return true; + } + + if (MathF.Abs(degrees - 270) < Constants.Epsilon) + { + this.Rotate270(source, destination, configuration); + return true; + } + + return false; + } + + /// + /// Rotates the image 270 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) + { + int width = source.Width; + int height = source.Height; + Rectangle destinationBounds = destination.Bounds(); + + ParallelHelper.IterateRows( + source.Bounds(), + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span 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]; + } + } + } + }); + } + + /// + /// Rotates the image 180 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) + { + int width = source.Width; + int height = source.Height; + + ParallelHelper.IterateRows( + source.Bounds(), + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y); + Span targetRow = destination.GetPixelRowSpan(height - y - 1); + + for (int x = 0; x < width; x++) + { + targetRow[width - x - 1] = sourceRow[x]; + } + } + }); + } + + /// + /// Rotates the image 90 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) + { + int width = source.Width; + int height = source.Height; + Rectangle destinationBounds = destination.Bounds(); + + ParallelHelper.IterateRows( + source.Bounds(), + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y); + int newX = height - y - 1; + for (int x = 0; x < width; x++) + { + if (destinationBounds.Contains(newX, x)) + { + destination[newX, x] = sourceRow[x]; + } + } + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index c7b1d7410..35c3717ee 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -9,14 +9,12 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides methods that allow the skewing of images. + /// Defines a skew transformation applicable to an . /// - /// The pixel format. - internal class SkewProcessor : AffineTransformProcessor - where TPixel : struct, IPixel + internal class SkewProcessor : AffineTransformProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The angle in degrees to perform the skew along the x-axis. /// The angle in degrees to perform the skew along the y-axis. @@ -27,7 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The angle in degrees to perform the skew along the x-axis. /// The angle in degrees to perform the skew along the y-axis. diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs index 4973b90f4..286ada2e5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs @@ -16,6 +16,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) - => TransformProcessorHelpers.UpdateDimensionalMetData(destination); + => TransformProcessorHelpers.UpdateDimensionalMetadata(destination); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs index 7bb666bce..00c1227a6 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. /// The image to update - public static void UpdateDimensionalMetData(Image image) + public static void UpdateDimensionalMetadata(Image image) where TPixel : struct, IPixel { ExifProfile profile = image.Metadata.ExifProfile; diff --git a/src/ImageSharp/Processing/RotateExtensions.cs b/src/ImageSharp/Processing/RotateExtensions.cs index 398a634d1..cb637a1b8 100644 --- a/src/ImageSharp/Processing/RotateExtensions.cs +++ b/src/ImageSharp/Processing/RotateExtensions.cs @@ -1,48 +1,44 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of rotate operations to the type. + /// Adds extensions that allow the application of rotate operations to the type. /// public static class RotateExtensions { /// /// Rotates and flips an image by the given instructions. /// - /// The pixel format. /// The image to rotate. /// The to perform the rotation. /// The - public static IImageProcessingContext Rotate(this IImageProcessingContext source, RotateMode rotateMode) - where TPixel : struct, IPixel - => Rotate(source, (float)rotateMode); + public static IImageProcessingContext Rotate(this IImageProcessingContext source, RotateMode rotateMode) => + Rotate(source, (float)rotateMode); /// /// Rotates an image by the given angle in degrees. /// - /// The pixel format. /// The image to rotate. /// The angle in degrees to perform the rotation. /// The - public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees) - where TPixel : struct, IPixel - => Rotate(source, degrees, KnownResamplers.Bicubic); + public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees) => + Rotate(source, degrees, KnownResamplers.Bicubic); /// /// Rotates an image by the given angle in degrees using the specified sampling algorithm. /// - /// The pixel format. /// The image to rotate. /// The angle in degrees to perform the rotation. /// The to perform the resampling. /// The - public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees, IResampler sampler) - where TPixel : struct, IPixel - => source.ApplyProcessor(new RotateProcessor(degrees, sampler, source.GetCurrentSize())); + public static IImageProcessingContext Rotate( + this IImageProcessingContext source, + float degrees, + IResampler sampler) => + source.ApplyProcessor(new RotateProcessor(degrees, sampler, source.GetCurrentSize())); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/RotateFlipExtensions.cs b/src/ImageSharp/Processing/RotateFlipExtensions.cs index 27ddc8de9..5030c942e 100644 --- a/src/ImageSharp/Processing/RotateFlipExtensions.cs +++ b/src/ImageSharp/Processing/RotateFlipExtensions.cs @@ -6,20 +6,18 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of rotate-flip operations to the type. + /// Adds extensions that allow the application of rotate-flip operations to the type. /// public static class RotateFlipExtensions { /// /// Rotates and flips an image by the given instructions. /// - /// The pixel format. /// The image to rotate, flip, or both. /// The to perform the rotation. /// The to perform the flip. /// The - public static IImageProcessingContext RotateFlip(this IImageProcessingContext source, RotateMode rotateMode, FlipMode flipMode) - where TPixel : struct, IPixel + public static IImageProcessingContext RotateFlip(this IImageProcessingContext source, RotateMode rotateMode, FlipMode flipMode) => source.Rotate(rotateMode).Flip(flipMode); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/SkewExtensions.cs b/src/ImageSharp/Processing/SkewExtensions.cs index 07e3c6087..7f378d248 100644 --- a/src/ImageSharp/Processing/SkewExtensions.cs +++ b/src/ImageSharp/Processing/SkewExtensions.cs @@ -1,39 +1,39 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of skew operations to the type. + /// Adds extensions that allow the application of skew operations to the type. /// public static class SkewExtensions { /// /// Skews an image by the given angles in degrees. /// - /// The pixel format. /// The image to skew. /// The angle in degrees to perform the skew along the x-axis. /// The angle in degrees to perform the skew along the y-axis. - /// The - public static IImageProcessingContext Skew(this IImageProcessingContext source, float degreesX, float degreesY) - where TPixel : struct, IPixel - => Skew(source, degreesX, degreesY, KnownResamplers.Bicubic); + /// The . + public static IImageProcessingContext + Skew(this IImageProcessingContext source, float degreesX, float degreesY) => + Skew(source, degreesX, degreesY, KnownResamplers.Bicubic); /// /// Skews an image by the given angles in degrees using the specified sampling algorithm. /// - /// The pixel format. /// The image to skew. /// The angle in degrees to perform the skew along the x-axis. /// The angle in degrees to perform the skew along the y-axis. /// The to perform the resampling. - /// The - public static IImageProcessingContext Skew(this IImageProcessingContext source, float degreesX, float degreesY, IResampler sampler) - where TPixel : struct, IPixel - => source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler, source.GetCurrentSize())); + /// The . + public static IImageProcessingContext Skew( + this IImageProcessingContext source, + float degreesX, + float degreesY, + IResampler sampler) => + source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler, source.GetCurrentSize())); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/TransformExtensions.cs b/src/ImageSharp/Processing/TransformExtensions.cs index db14b6baf..35f374d01 100644 --- a/src/ImageSharp/Processing/TransformExtensions.cs +++ b/src/ImageSharp/Processing/TransformExtensions.cs @@ -3,60 +3,53 @@ using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of composable transform operations to the type. + /// Adds extensions that allow the application of composable transform operations to the type. /// public static class TransformExtensions { /// /// Performs an affine transform of an image. /// - /// The pixel format. /// The image to transform. /// The affine transform builder. /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - AffineTransformBuilder builder) - where TPixel : struct, IPixel - => Transform(source, builder, KnownResamplers.Bicubic); + public static IImageProcessingContext Transform( + this IImageProcessingContext source, + AffineTransformBuilder builder) => + Transform(source, builder, KnownResamplers.Bicubic); /// /// Performs an affine transform of an image using the specified sampling algorithm. /// - /// The pixel format. - /// The . + /// The . /// The affine transform builder. /// The to perform the resampling. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, + /// The . + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, AffineTransformBuilder builder, - IResampler sampler) - where TPixel : struct, IPixel - => ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); + IResampler sampler) => + ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); /// /// Performs an affine transform of an image using the specified sampling algorithm. /// - /// The pixel format. - /// The . + /// The . /// The source rectangle /// The affine transform builder. /// The to perform the resampling. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, + /// The . + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, Rectangle sourceRectangle, AffineTransformBuilder builder, IResampler sampler) - where TPixel : struct, IPixel { Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); @@ -66,69 +59,61 @@ namespace SixLabors.ImageSharp.Processing /// /// Performs an affine transform of an image using the specified sampling algorithm. /// - /// The pixel format. - /// The . + /// The . /// The source rectangle /// The transformation matrix. /// The size of the result image. /// The to perform the resampling. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, + /// The . + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, Rectangle sourceRectangle, Matrix3x2 transform, Size targetDimensions, IResampler sampler) - where TPixel : struct, IPixel { return ctx.ApplyProcessor( - new AffineTransformProcessor(transform, sampler, targetDimensions), + new AffineTransformProcessor(transform, sampler, targetDimensions), sourceRectangle); } /// /// Performs a projective transform of an image. /// - /// The pixel format. /// The image to transform. /// The affine transform builder. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - ProjectiveTransformBuilder builder) - where TPixel : struct, IPixel - => Transform(source, builder, KnownResamplers.Bicubic); + /// The . + public static IImageProcessingContext Transform( + this IImageProcessingContext source, + ProjectiveTransformBuilder builder) => + Transform(source, builder, KnownResamplers.Bicubic); /// /// Performs a projective transform of an image using the specified sampling algorithm. /// - /// The pixel format. - /// The . + /// The . /// The projective transform builder. /// The to perform the resampling. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, + /// The . + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, ProjectiveTransformBuilder builder, - IResampler sampler) - where TPixel : struct, IPixel - => ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); + IResampler sampler) => + ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); /// /// Performs a projective transform of an image using the specified sampling algorithm. /// - /// The pixel format. - /// The . + /// The . /// The source rectangle /// The projective transform builder. /// The to perform the resampling. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, + /// The . + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, Rectangle sourceRectangle, ProjectiveTransformBuilder builder, IResampler sampler) - where TPixel : struct, IPixel { Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); @@ -138,23 +123,21 @@ namespace SixLabors.ImageSharp.Processing /// /// Performs a projective transform of an image using the specified sampling algorithm. /// - /// The pixel format. - /// The . + /// The . /// The source rectangle /// The transformation matrix. /// The size of the result image. /// The to perform the resampling. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, + /// The . + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, Rectangle sourceRectangle, Matrix4x4 transform, Size targetDimensions, IResampler sampler) - where TPixel : struct, IPixel { return ctx.ApplyProcessor( - new ProjectiveTransformProcessor(transform, sampler, targetDimensions), + new ProjectiveTransformProcessor(transform, sampler, targetDimensions), sourceRectangle); } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs index b07f50883..ee04d4388 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -79,7 +79,8 @@ namespace SixLabors.ImageSharp.Tests .AppendTranslation(new PointF(10, 10)); // Apply a background color so we can see the translation. - blend.Mutate(x => x.Transform(builder).BackgroundColor(NamedColors.HotPink)); + blend.Mutate(x => x.Transform(builder)); + blend.Mutate(x => x.BackgroundColor(NamedColors.HotPink)); // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs index 6731debd3..1c3bf2e90 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void CropWidthHeightCropProcessorWithRectangleSet(int width, int height) { this.operations.Crop(width, height); - CropProcessor processor = this.Verify>(); + CropProcessor processor = this.Verify(); Assert.Equal(new Rectangle(0, 0, width, height), processor.CropRectangle); } @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms { var cropRectangle = new Rectangle(x, y, width, height); this.operations.Crop(cropRectangle); - CropProcessor processor = this.Verify>(); + CropProcessor processor = this.Verify(); Assert.Equal(cropRectangle, processor.CropRectangle); } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs index 03a8628a5..aa684acd5 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void EntropyCropThresholdFloatEntropyCropProcessorWithThreshold(float threshold) { this.operations.EntropyCrop(threshold); - EntropyCropProcessor processor = this.Verify>(); + EntropyCropProcessor processor = this.Verify(); Assert.Equal(threshold, processor.Threshold); } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs index 39adcaa3f..9fe2977ad 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void Flip_degreesFloat_RotateProcessorWithAnglesSetAndExpandTrue(FlipMode flip) { this.operations.Flip(flip); - FlipProcessor flipProcessor = this.Verify>(); + FlipProcessor flipProcessor = this.Verify(); Assert.Equal(flip, flipProcessor.FlipMode); } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs index 82d768255..b870ddd08 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -1,15 +1,13 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; + using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Processors.Transforms; - public class PadTest : BaseImageOperationsExtensionTest { [Fact] @@ -20,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms IResampler sampler = KnownResamplers.NearestNeighbor; this.operations.Pad(width, height); - ResizeProcessor resizeProcessor = this.Verify>(); + ResizeProcessor resizeProcessor = this.Verify(); Assert.Equal(width, resizeProcessor.Width); Assert.Equal(height, resizeProcessor.Height); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs index dccf7afa6..079ff67b7 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs @@ -26,8 +26,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void RotateDegreesFloatRotateProcessorWithAnglesSet(RotateMode angle, FlipMode flip, float expectedAngle) { this.operations.RotateFlip(angle, flip); - RotateProcessor rotateProcessor = this.Verify>(0); - FlipProcessor flipProcessor = this.Verify>(1); + RotateProcessor rotateProcessor = this.Verify(0); + FlipProcessor flipProcessor = this.Verify(1); Assert.Equal(expectedAngle, rotateProcessor.Degrees); Assert.Equal(flip, flipProcessor.FlipMode); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs index ae312d723..3e2b85971 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void RotateDegreesFloatRotateProcessorWithAnglesSet(float angle) { this.operations.Rotate(angle); - RotateProcessor processor = this.Verify>(); + RotateProcessor processor = this.Verify(); Assert.Equal(angle, processor.Degrees); } @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void RotateRotateTypeRotateProcessorWithAnglesConvertedFromEnum(RotateMode angle, float expectedAngle) { this.operations.Rotate(angle); // is this api needed ??? - RotateProcessor processor = this.Verify>(); + RotateProcessor processor = this.Verify(); Assert.Equal(expectedAngle, processor.Degrees); } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs index 73754b971..38033e80d 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void SkewXYCreateSkewProcessorWithAnglesSet() { this.operations.Skew(10, 20); - SkewProcessor processor = this.Verify>(); + SkewProcessor processor = this.Verify(); Assert.Equal(10, processor.DegreesX); Assert.Equal(20, processor.DegreesY); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs index a62f4fc7c..3ac9af960 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType); Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType); - TransformProcessorHelpers.UpdateDimensionalMetData(img); + TransformProcessorHelpers.UpdateDimensionalMetadata(img); Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelXDimension).DataType); Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelYDimension).DataType);