mirror of https://github.com/SixLabors/ImageSharp
34 changed files with 953 additions and 775 deletions
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the application of cropping operations to the <see cref="Image{TPixel}"/> type.
|
|||
/// Adds extensions that allow the application of cropping operations to the <see cref="Image"/> type.
|
|||
/// </summary>
|
|||
public static class CropExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Crops an image to the given width and height.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
|||
public static IImageProcessingContext<TPixel> Crop<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> 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)); |
|||
|
|||
/// <summary>
|
|||
/// Crops an image to the given rectangle.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image to crop.</param>
|
|||
/// <param name="cropRectangle">
|
|||
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to retain.
|
|||
/// </param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
|||
public static IImageProcessingContext<TPixel> Crop<TPixel>(this IImageProcessingContext<TPixel> source, Rectangle cropRectangle) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.ApplyProcessor(new CropProcessor<TPixel>(cropRectangle, source.GetCurrentSize())); |
|||
public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) => |
|||
source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the application of entropy cropping operations to the <see cref="Image{TPixel}"/> type.
|
|||
/// Adds extensions that allow the application of entropy cropping operations to the <see cref="Image"/> type.
|
|||
/// </summary>
|
|||
public static class EntropyCropExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Crops an image to the area of greatest entropy using a threshold for entropic density of <value>.5F</value>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image to crop.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
|||
public static IImageProcessingContext<TPixel> EntropyCrop<TPixel>(this IImageProcessingContext<TPixel> source) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.ApplyProcessor(new EntropyCropProcessor<TPixel>()); |
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source) => |
|||
source.ApplyProcessor(new EntropyCropProcessor()); |
|||
|
|||
/// <summary>
|
|||
/// Crops an image to the area of greatest entropy.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image to crop.</param>
|
|||
/// <param name="threshold">The threshold for entropic density.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
|||
public static IImageProcessingContext<TPixel> EntropyCrop<TPixel>(this IImageProcessingContext<TPixel> source, float threshold) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.ApplyProcessor(new EntropyCropProcessor<TPixel>(threshold)); |
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source, float threshold) => |
|||
source.ApplyProcessor(new EntropyCropProcessor(threshold)); |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the application of flipping operations to the <see cref="Image{TPixel}"/> type.
|
|||
/// Adds extensions that allow the application of flipping operations to the <see cref="Image"/> type.
|
|||
/// </summary>
|
|||
public static class FlipExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Flips an image by the given instructions.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image to rotate, flip, or both.</param>
|
|||
/// <param name="flipMode">The <see cref="FlipMode"/> to perform the flip.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
|||
public static IImageProcessingContext<TPixel> Flip<TPixel>(this IImageProcessingContext<TPixel> source, FlipMode flipMode) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.ApplyProcessor(new FlipProcessor<TPixel>(flipMode)); |
|||
public static IImageProcessingContext Flip(this IImageProcessingContext source, FlipMode flipMode) |
|||
=> source.ApplyProcessor(new FlipProcessor(flipMode)); |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Provides the base methods to perform affine transforms on an image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class AffineTransformProcessor<TPixel> : TransformProcessorBase<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
public AffineTransformProcessor(AffineTransformProcessor definition) |
|||
{ |
|||
this.Definition = definition; |
|||
} |
|||
|
|||
private Size TargetDimensions => this.Definition.TargetDimensions; |
|||
|
|||
private Matrix3x2 TransformMatrix => this.Definition.TransformMatrix; |
|||
|
|||
protected AffineTransformProcessor Definition { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle) |
|||
{ |
|||
// We will always be creating the clone even for mutate because we may need to resize the canvas
|
|||
IEnumerable<ImageFrame<TPixel>> frames = |
|||
source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.TargetDimensions, x.Metadata.DeepClone())); |
|||
|
|||
// Use the overload to prevent an extra frame being added
|
|||
return new Image<TPixel>(source.GetConfiguration(), source.Metadata.DeepClone(), frames); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply( |
|||
ImageFrame<TPixel> source, |
|||
ImageFrame<TPixel> 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<TPixel> 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<Vector4>( |
|||
targetBounds, |
|||
configuration, |
|||
(rows, vectorBuffer) => |
|||
{ |
|||
Span<Vector4> vectorSpan = vectorBuffer.Span; |
|||
for (int y = rows.Min; y < rows.Max; y++) |
|||
{ |
|||
Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y); |
|||
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); |
|||
} |
|||
}); |
|||
} |
|||
finally |
|||
{ |
|||
kernel.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class AutoOrientProcessor<TPixel> : ImageProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
public sealed class AutoOrientProcessor : IImageProcessor |
|||
{ |
|||
/// <inheritdoc/>
|
|||
protected override void BeforeImageApply(Image<TPixel> source, Rectangle sourceRectangle) |
|||
/// <inheritdoc />
|
|||
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
OrientationMode orientation = GetExifOrientation(source); |
|||
Size size = sourceRectangle.Size; |
|||
switch (orientation) |
|||
{ |
|||
case OrientationMode.TopRight: |
|||
new FlipProcessor<TPixel>(FlipMode.Horizontal).Apply(source, sourceRectangle); |
|||
break; |
|||
|
|||
case OrientationMode.BottomRight: |
|||
new RotateProcessor<TPixel>((int)RotateMode.Rotate180, size).Apply(source, sourceRectangle); |
|||
break; |
|||
|
|||
case OrientationMode.BottomLeft: |
|||
new FlipProcessor<TPixel>(FlipMode.Vertical).Apply(source, sourceRectangle); |
|||
break; |
|||
|
|||
case OrientationMode.LeftTop: |
|||
new RotateProcessor<TPixel>((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle); |
|||
new FlipProcessor<TPixel>(FlipMode.Horizontal).Apply(source, sourceRectangle); |
|||
break; |
|||
|
|||
case OrientationMode.RightTop: |
|||
new RotateProcessor<TPixel>((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle); |
|||
break; |
|||
|
|||
case OrientationMode.RightBottom: |
|||
new FlipProcessor<TPixel>(FlipMode.Vertical).Apply(source, sourceRectangle); |
|||
new RotateProcessor<TPixel>((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle); |
|||
break; |
|||
|
|||
case OrientationMode.LeftBottom: |
|||
new RotateProcessor<TPixel>((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle); |
|||
break; |
|||
|
|||
case OrientationMode.Unknown: |
|||
case OrientationMode.TopLeft: |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> sourceBase, Rectangle sourceRectangle, Configuration config) |
|||
{ |
|||
// All processing happens at the image level within BeforeImageApply();
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the current EXIF orientation
|
|||
/// </summary>
|
|||
/// <param name="source">The image to auto rotate.</param>
|
|||
/// <returns>The <see cref="OrientationMode"/></returns>
|
|||
private static OrientationMode GetExifOrientation(Image<TPixel> 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<TPixel>(); |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class AutoOrientProcessor<TPixel> : ImageProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
protected override void BeforeImageApply(Image<TPixel> 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; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> sourceBase, Rectangle sourceRectangle, Configuration config) |
|||
{ |
|||
// All processing happens at the image level within BeforeImageApply();
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the current EXIF orientation
|
|||
/// </summary>
|
|||
/// <param name="source">The image to auto rotate.</param>
|
|||
/// <returns>The <see cref="OrientationMode"/></returns>
|
|||
private static OrientationMode GetExifOrientation(Image<TPixel> 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; |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Provides methods to allow the cropping of an image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class CropProcessor<TPixel> : TransformProcessorBase<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly CropProcessor definition; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CropProcessor{TPixel}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="definition">The <see cref="CropProcessor"/>.</param>
|
|||
public CropProcessor(CropProcessor definition) |
|||
{ |
|||
this.definition = definition; |
|||
} |
|||
|
|||
private Rectangle CropRectangle => this.definition.CropRectangle; |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle) |
|||
{ |
|||
// We will always be creating the clone even for mutate because we may need to resize the canvas
|
|||
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.Metadata.DeepClone())); |
|||
|
|||
// Use the overload to prevent an extra frame being added
|
|||
return new Image<TPixel>(source.GetConfiguration(), source.Metadata.DeepClone(), frames); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> 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<TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(rect.Left); |
|||
Span<TPixel> targetRow = destination.GetPixelRowSpan(y - rect.Top); |
|||
sourceRow.Slice(0, rect.Width).CopyTo(targetRow); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Provides methods to allow the cropping of an image to preserve areas of highest entropy.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class EntropyCropProcessor<TPixel> : ImageProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly EntropyCropProcessor definition; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="EntropyCropProcessor{TPixel}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="definition">The <see cref="EntropyCropProcessor"/>.</param>
|
|||
public EntropyCropProcessor(EntropyCropProcessor definition) |
|||
{ |
|||
this.definition = definition; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void BeforeImageApply(Image<TPixel> 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<TPixel> 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<TPixel>(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); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|||
{ |
|||
// All processing happens at the image level within BeforeImageApply();
|
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Provides methods that allow the flipping of an image around its center point.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class FlipProcessor<TPixel> : ImageProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly FlipProcessor definition; |
|||
|
|||
public FlipProcessor(FlipProcessor definition) |
|||
{ |
|||
this.definition = definition; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> 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; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The source image to apply the process to.</param>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
private void FlipX(ImageFrame<TPixel> source, Configuration configuration) |
|||
{ |
|||
int height = source.Height; |
|||
|
|||
using (IMemoryOwner<TPixel> tempBuffer = configuration.MemoryAllocator.Allocate<TPixel>(source.Width)) |
|||
{ |
|||
Span<TPixel> temp = tempBuffer.Memory.Span; |
|||
|
|||
for (int yTop = 0; yTop < height / 2; yTop++) |
|||
{ |
|||
int yBottom = height - yTop - 1; |
|||
Span<TPixel> topRow = source.GetPixelRowSpan(yBottom); |
|||
Span<TPixel> bottomRow = source.GetPixelRowSpan(yTop); |
|||
topRow.CopyTo(temp); |
|||
bottomRow.CopyTo(topRow); |
|||
temp.CopyTo(bottomRow); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The source image to apply the process to.</param>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
private void FlipY(ImageFrame<TPixel> source, Configuration configuration) |
|||
{ |
|||
ParallelHelper.IterateRows( |
|||
source.Bounds(), |
|||
configuration, |
|||
rows => |
|||
{ |
|||
for (int y = rows.Min; y < rows.Max; y++) |
|||
{ |
|||
source.GetPixelRowSpan(y).Reverse(); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Provides the base methods to perform non-affine transforms on an image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class ProjectiveTransformProcessor<TPixel> : TransformProcessorBase<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly ProjectiveTransformProcessor definition; |
|||
|
|||
public ProjectiveTransformProcessor(ProjectiveTransformProcessor definition) |
|||
{ |
|||
this.definition = definition; |
|||
} |
|||
|
|||
private Size TargetDimensions => this.definition.TargetDimensions; |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle) |
|||
{ |
|||
// We will always be creating the clone even for mutate because we may need to resize the canvas
|
|||
IEnumerable<ImageFrame<TPixel>> frames = |
|||
source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.Metadata.DeepClone())); |
|||
|
|||
// Use the overload to prevent an extra frame being added
|
|||
return new Image<TPixel>(source.GetConfiguration(), source.Metadata.DeepClone(), frames); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> 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<TPixel> 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<Vector4>( |
|||
targetBounds, |
|||
configuration, |
|||
(rows, vectorBuffer) => |
|||
{ |
|||
Span<Vector4> vectorSpan = vectorBuffer.Span; |
|||
for (int y = rows.Min; y < rows.Max; y++) |
|||
{ |
|||
Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y); |
|||
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); |
|||
} |
|||
}); |
|||
} |
|||
finally |
|||
{ |
|||
kernel.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Provides methods that allow the rotating of images.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class RotateProcessor<TPixel> : AffineTransformProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
public RotateProcessor(RotateProcessor definition) |
|||
: base(definition) |
|||
{ |
|||
this.Degrees = definition.Degrees; |
|||
} |
|||
|
|||
private float Degrees { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration) |
|||
{ |
|||
if (this.OptimizedApply(source, destination, configuration)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
base.OnFrameApply(source, destination, sourceRectangle, configuration); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void AfterImageApply(Image<TPixel> source, Image<TPixel> 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); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Wraps a given angle in degrees so that it falls withing the 0-360 degree range
|
|||
/// </summary>
|
|||
/// <param name="degrees">The angle of rotation in degrees.</param>
|
|||
/// <returns>The <see cref="float"/>.</returns>
|
|||
private static float WrapDegrees(float degrees) |
|||
{ |
|||
degrees %= 360; |
|||
|
|||
while (degrees < 0) |
|||
{ |
|||
degrees += 360; |
|||
} |
|||
|
|||
return degrees; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees.
|
|||
/// </summary>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="destination">The destination image.</param>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="bool" />
|
|||
/// </returns>
|
|||
private bool OptimizedApply(ImageFrame<TPixel> source, ImageFrame<TPixel> 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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates the image 270 degrees clockwise at the centre point.
|
|||
/// </summary>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="destination">The destination image.</param>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
private void Rotate270(ImageFrame<TPixel> source, ImageFrame<TPixel> 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<TPixel> 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]; |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates the image 180 degrees clockwise at the centre point.
|
|||
/// </summary>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="destination">The destination image.</param>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
private void Rotate180(ImageFrame<TPixel> source, ImageFrame<TPixel> 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<TPixel> sourceRow = source.GetPixelRowSpan(y); |
|||
Span<TPixel> targetRow = destination.GetPixelRowSpan(height - y - 1); |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
targetRow[width - x - 1] = sourceRow[x]; |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates the image 90 degrees clockwise at the centre point.
|
|||
/// </summary>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="destination">The destination image.</param>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
private void Rotate90(ImageFrame<TPixel> source, ImageFrame<TPixel> 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<TPixel> 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]; |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the application of rotate operations to the <see cref="Image{TPixel}"/> type.
|
|||
/// Adds extensions that allow the application of rotate operations to the <see cref="Image"/> type.
|
|||
/// </summary>
|
|||
public static class RotateExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Rotates and flips an image by the given instructions.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image to rotate.</param>
|
|||
/// <param name="rotateMode">The <see cref="RotateMode"/> to perform the rotation.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
|||
public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, RotateMode rotateMode) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> Rotate(source, (float)rotateMode); |
|||
public static IImageProcessingContext Rotate(this IImageProcessingContext source, RotateMode rotateMode) => |
|||
Rotate(source, (float)rotateMode); |
|||
|
|||
/// <summary>
|
|||
/// Rotates an image by the given angle in degrees.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image to rotate.</param>
|
|||
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
|||
public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, float degrees) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> Rotate(source, degrees, KnownResamplers.Bicubic); |
|||
public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees) => |
|||
Rotate(source, degrees, KnownResamplers.Bicubic); |
|||
|
|||
/// <summary>
|
|||
/// Rotates an image by the given angle in degrees using the specified sampling algorithm.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image to rotate.</param>
|
|||
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
|
|||
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
|||
public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, float degrees, IResampler sampler) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.ApplyProcessor(new RotateProcessor<TPixel>(degrees, sampler, source.GetCurrentSize())); |
|||
public static IImageProcessingContext Rotate( |
|||
this IImageProcessingContext source, |
|||
float degrees, |
|||
IResampler sampler) => |
|||
source.ApplyProcessor(new RotateProcessor(degrees, sampler, source.GetCurrentSize())); |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the application of skew operations to the <see cref="Image{TPixel}"/> type.
|
|||
/// Adds extensions that allow the application of skew operations to the <see cref="Image"/> type.
|
|||
/// </summary>
|
|||
public static class SkewExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Skews an image by the given angles in degrees.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image to skew.</param>
|
|||
/// <param name="degreesX">The angle in degrees to perform the skew along the x-axis.</param>
|
|||
/// <param name="degreesY">The angle in degrees to perform the skew along the y-axis.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
|||
public static IImageProcessingContext<TPixel> Skew<TPixel>(this IImageProcessingContext<TPixel> source, float degreesX, float degreesY) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> Skew(source, degreesX, degreesY, KnownResamplers.Bicubic); |
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext |
|||
Skew(this IImageProcessingContext source, float degreesX, float degreesY) => |
|||
Skew(source, degreesX, degreesY, KnownResamplers.Bicubic); |
|||
|
|||
/// <summary>
|
|||
/// Skews an image by the given angles in degrees using the specified sampling algorithm.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image to skew.</param>
|
|||
/// <param name="degreesX">The angle in degrees to perform the skew along the x-axis.</param>
|
|||
/// <param name="degreesY">The angle in degrees to perform the skew along the y-axis.</param>
|
|||
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
|||
public static IImageProcessingContext<TPixel> Skew<TPixel>(this IImageProcessingContext<TPixel> source, float degreesX, float degreesY, IResampler sampler) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.ApplyProcessor(new SkewProcessor<TPixel>(degreesX, degreesY, sampler, source.GetCurrentSize())); |
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Skew( |
|||
this IImageProcessingContext source, |
|||
float degreesX, |
|||
float degreesY, |
|||
IResampler sampler) => |
|||
source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler, source.GetCurrentSize())); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue