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.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
||||
using SixLabors.Primitives; |
using SixLabors.Primitives; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
public static class CropExtensions |
public static class CropExtensions |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Crops an image to the given width and height.
|
/// Crops an image to the given width and height.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
/// <param name="source">The image to resize.</param>
|
/// <param name="source">The image to resize.</param>
|
||||
/// <param name="width">The target image width.</param>
|
/// <param name="width">The target image width.</param>
|
||||
/// <param name="height">The target image height.</param>
|
/// <param name="height">The target image height.</param>
|
||||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
||||
public static IImageProcessingContext<TPixel> Crop<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height) |
public static IImageProcessingContext Crop(this IImageProcessingContext source, int width, int height) => |
||||
where TPixel : struct, IPixel<TPixel> |
Crop(source, new Rectangle(0, 0, width, height)); |
||||
=> Crop(source, new Rectangle(0, 0, width, height)); |
|
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Crops an image to the given rectangle.
|
/// Crops an image to the given rectangle.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
/// <param name="source">The image to crop.</param>
|
/// <param name="source">The image to crop.</param>
|
||||
/// <param name="cropRectangle">
|
/// <param name="cropRectangle">
|
||||
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to retain.
|
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to retain.
|
||||
/// </param>
|
/// </param>
|
||||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
||||
public static IImageProcessingContext<TPixel> Crop<TPixel>(this IImageProcessingContext<TPixel> source, Rectangle cropRectangle) |
public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) => |
||||
where TPixel : struct, IPixel<TPixel> |
source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); |
||||
=> source.ApplyProcessor(new CropProcessor<TPixel>(cropRectangle, source.GetCurrentSize())); |
|
||||
} |
} |
||||
} |
} |
||||
@ -1,35 +1,30 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
public static class EntropyCropExtensions |
public static class EntropyCropExtensions |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Crops an image to the area of greatest entropy using a threshold for entropic density of <value>.5F</value>.
|
/// Crops an image to the area of greatest entropy using a threshold for entropic density of <value>.5F</value>.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
/// <param name="source">The image to crop.</param>
|
/// <param name="source">The image to crop.</param>
|
||||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
public static IImageProcessingContext<TPixel> EntropyCrop<TPixel>(this IImageProcessingContext<TPixel> source) |
public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source) => |
||||
where TPixel : struct, IPixel<TPixel> |
source.ApplyProcessor(new EntropyCropProcessor()); |
||||
=> source.ApplyProcessor(new EntropyCropProcessor<TPixel>()); |
|
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Crops an image to the area of greatest entropy.
|
/// Crops an image to the area of greatest entropy.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
/// <param name="source">The image to crop.</param>
|
/// <param name="source">The image to crop.</param>
|
||||
/// <param name="threshold">The threshold for entropic density.</param>
|
/// <param name="threshold">The threshold for entropic density.</param>
|
||||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
public static IImageProcessingContext<TPixel> EntropyCrop<TPixel>(this IImageProcessingContext<TPixel> source, float threshold) |
public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source, float threshold) => |
||||
where TPixel : struct, IPixel<TPixel> |
source.ApplyProcessor(new EntropyCropProcessor(threshold)); |
||||
=> source.ApplyProcessor(new EntropyCropProcessor<TPixel>(threshold)); |
|
||||
} |
} |
||||
} |
} |
||||
@ -1,25 +1,22 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
public static class FlipExtensions |
public static class FlipExtensions |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Flips an image by the given instructions.
|
/// Flips an image by the given instructions.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
/// <param name="source">The image to rotate, flip, or both.</param>
|
/// <param name="source">The image to rotate, flip, or both.</param>
|
||||
/// <param name="flipMode">The <see cref="FlipMode"/> to perform the flip.</param>
|
/// <param name="flipMode">The <see cref="FlipMode"/> to perform the flip.</param>
|
||||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
||||
public static IImageProcessingContext<TPixel> Flip<TPixel>(this IImageProcessingContext<TPixel> source, FlipMode flipMode) |
public static IImageProcessingContext Flip(this IImageProcessingContext source, FlipMode flipMode) |
||||
where TPixel : struct, IPixel<TPixel> |
=> source.ApplyProcessor(new FlipProcessor(flipMode)); |
||||
=> source.ApplyProcessor(new FlipProcessor<TPixel>(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.
|
// 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 SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
using SixLabors.ImageSharp.Processing.Processors; |
using SixLabors.ImageSharp.Processing.Processors; |
||||
using SixLabors.Primitives; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image.
|
/// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
public sealed class AutoOrientProcessor : IImageProcessor |
||||
internal class AutoOrientProcessor<TPixel> : ImageProcessor<TPixel> |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
{ |
||||
/// <inheritdoc/>
|
/// <inheritdoc />
|
||||
protected override void BeforeImageApply(Image<TPixel> source, Rectangle sourceRectangle) |
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
{ |
{ |
||||
OrientationMode orientation = GetExifOrientation(source); |
return new AutoOrientProcessor<TPixel>(); |
||||
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; |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -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.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
public static class RotateExtensions |
public static class RotateExtensions |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Rotates and flips an image by the given instructions.
|
/// Rotates and flips an image by the given instructions.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
/// <param name="source">The image to rotate.</param>
|
/// <param name="source">The image to rotate.</param>
|
||||
/// <param name="rotateMode">The <see cref="RotateMode"/> to perform the rotation.</param>
|
/// <param name="rotateMode">The <see cref="RotateMode"/> to perform the rotation.</param>
|
||||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
||||
public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, RotateMode rotateMode) |
public static IImageProcessingContext Rotate(this IImageProcessingContext source, RotateMode rotateMode) => |
||||
where TPixel : struct, IPixel<TPixel> |
Rotate(source, (float)rotateMode); |
||||
=> Rotate(source, (float)rotateMode); |
|
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Rotates an image by the given angle in degrees.
|
/// Rotates an image by the given angle in degrees.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
/// <param name="source">The image to rotate.</param>
|
/// <param name="source">The image to rotate.</param>
|
||||
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
|
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
|
||||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
||||
public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, float degrees) |
public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees) => |
||||
where TPixel : struct, IPixel<TPixel> |
Rotate(source, degrees, KnownResamplers.Bicubic); |
||||
=> Rotate(source, degrees, KnownResamplers.Bicubic); |
|
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Rotates an image by the given angle in degrees using the specified sampling algorithm.
|
/// Rotates an image by the given angle in degrees using the specified sampling algorithm.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
/// <param name="source">The image to rotate.</param>
|
/// <param name="source">The image to rotate.</param>
|
||||
/// <param name="degrees">The angle in degrees to perform the rotation.</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>
|
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
|
||||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
||||
public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, float degrees, IResampler sampler) |
public static IImageProcessingContext Rotate( |
||||
where TPixel : struct, IPixel<TPixel> |
this IImageProcessingContext source, |
||||
=> source.ApplyProcessor(new RotateProcessor<TPixel>(degrees, sampler, source.GetCurrentSize())); |
float degrees, |
||||
|
IResampler sampler) => |
||||
|
source.ApplyProcessor(new RotateProcessor(degrees, sampler, source.GetCurrentSize())); |
||||
} |
} |
||||
} |
} |
||||
@ -1,39 +1,39 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
public static class SkewExtensions |
public static class SkewExtensions |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Skews an image by the given angles in degrees.
|
/// Skews an image by the given angles in degrees.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
/// <param name="source">The image to skew.</param>
|
/// <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="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="degreesY">The angle in degrees to perform the skew along the y-axis.</param>
|
||||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
public static IImageProcessingContext<TPixel> Skew<TPixel>(this IImageProcessingContext<TPixel> source, float degreesX, float degreesY) |
public static IImageProcessingContext |
||||
where TPixel : struct, IPixel<TPixel> |
Skew(this IImageProcessingContext source, float degreesX, float degreesY) => |
||||
=> Skew(source, degreesX, degreesY, KnownResamplers.Bicubic); |
Skew(source, degreesX, degreesY, KnownResamplers.Bicubic); |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Skews an image by the given angles in degrees using the specified sampling algorithm.
|
/// Skews an image by the given angles in degrees using the specified sampling algorithm.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
/// <param name="source">The image to skew.</param>
|
/// <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="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="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>
|
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
|
||||
/// <returns>The <see cref="Image{TPixel}"/></returns>
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
public static IImageProcessingContext<TPixel> Skew<TPixel>(this IImageProcessingContext<TPixel> source, float degreesX, float degreesY, IResampler sampler) |
public static IImageProcessingContext Skew( |
||||
where TPixel : struct, IPixel<TPixel> |
this IImageProcessingContext source, |
||||
=> source.ApplyProcessor(new SkewProcessor<TPixel>(degreesX, degreesY, sampler, source.GetCurrentSize())); |
float degreesX, |
||||
|
float degreesY, |
||||
|
IResampler sampler) => |
||||
|
source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler, source.GetCurrentSize())); |
||||
} |
} |
||||
} |
} |
||||
Loading…
Reference in new issue