|
|
@ -2,7 +2,9 @@ |
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
|
|
|
|
|
|
using System; |
|
|
using System; |
|
|
|
|
|
using System.Runtime.CompilerServices; |
|
|
using SixLabors.ImageSharp.Advanced; |
|
|
using SixLabors.ImageSharp.Advanced; |
|
|
|
|
|
using SixLabors.ImageSharp.Memory; |
|
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|
|
using SixLabors.ImageSharp.PixelFormats; |
|
|
using SixLabors.ImageSharp.PixelFormats; |
|
|
|
|
|
|
|
|
@ -15,6 +17,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
|
internal class RotateProcessor<TPixel> : AffineTransformProcessor<TPixel> |
|
|
internal class RotateProcessor<TPixel> : AffineTransformProcessor<TPixel> |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
{ |
|
|
{ |
|
|
|
|
|
private float degrees; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class.
|
|
|
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
@ -24,11 +28,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
|
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
|
|
|
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
|
|
|
public RotateProcessor(Configuration configuration, RotateProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) |
|
|
public RotateProcessor(Configuration configuration, RotateProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) |
|
|
: base(configuration, definition, source, sourceRectangle) |
|
|
: base(configuration, definition, source, sourceRectangle) |
|
|
|
|
|
=> this.degrees = definition.Degrees; |
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
|
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) |
|
|
{ |
|
|
{ |
|
|
this.Degrees = definition.Degrees; |
|
|
if (this.OptimizedApply(source, destination, this.Configuration)) |
|
|
} |
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private float Degrees { get; } |
|
|
base.OnFrameApply(source, destination); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
/// <inheritdoc/>
|
|
|
protected override void AfterImageApply(Image<TPixel> destination) |
|
|
protected override void AfterImageApply(Image<TPixel> destination) |
|
|
@ -39,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon) |
|
|
if (MathF.Abs(WrapDegrees(this.degrees)) < Constants.Epsilon) |
|
|
{ |
|
|
{ |
|
|
// No need to do anything so return.
|
|
|
// No need to do anything so return.
|
|
|
return; |
|
|
return; |
|
|
@ -50,17 +61,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
|
base.AfterImageApply(destination); |
|
|
base.AfterImageApply(destination); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
|
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) |
|
|
|
|
|
{ |
|
|
|
|
|
if (this.OptimizedApply(source, destination, this.Configuration)) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
base.OnFrameApply(source, destination); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Wraps a given angle in degrees so that it falls withing the 0-360 degree range
|
|
|
/// Wraps a given angle in degrees so that it falls withing the 0-360 degree range
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
|
Configuration configuration) |
|
|
Configuration configuration) |
|
|
{ |
|
|
{ |
|
|
// Wrap the degrees to keep within 0-360 so we can apply optimizations when possible.
|
|
|
// Wrap the degrees to keep within 0-360 so we can apply optimizations when possible.
|
|
|
float degrees = WrapDegrees(this.Degrees); |
|
|
float degrees = WrapDegrees(this.degrees); |
|
|
|
|
|
|
|
|
if (MathF.Abs(degrees) < Constants.Epsilon) |
|
|
if (MathF.Abs(degrees) < Constants.Epsilon) |
|
|
{ |
|
|
{ |
|
|
@ -131,25 +131,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
|
/// <param name="configuration">The configuration.</param>
|
|
|
/// <param name="configuration">The configuration.</param>
|
|
|
private void Rotate180(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) |
|
|
private void Rotate180(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) |
|
|
{ |
|
|
{ |
|
|
int width = source.Width; |
|
|
|
|
|
int height = source.Height; |
|
|
|
|
|
|
|
|
|
|
|
ParallelRowIterator.IterateRows( |
|
|
ParallelRowIterator.IterateRows( |
|
|
source.Bounds(), |
|
|
source.Bounds(), |
|
|
configuration, |
|
|
configuration, |
|
|
rows => |
|
|
new Rotate180RowIntervalAction(source.Width, source.Height, source, destination)); |
|
|
{ |
|
|
|
|
|
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>
|
|
|
/// <summary>
|
|
|
@ -160,31 +145,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
|
/// <param name="configuration">The configuration.</param>
|
|
|
/// <param name="configuration">The configuration.</param>
|
|
|
private void Rotate270(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) |
|
|
private void Rotate270(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) |
|
|
{ |
|
|
{ |
|
|
int width = source.Width; |
|
|
|
|
|
int height = source.Height; |
|
|
|
|
|
Rectangle destinationBounds = destination.Bounds(); |
|
|
|
|
|
|
|
|
|
|
|
ParallelRowIterator.IterateRows( |
|
|
ParallelRowIterator.IterateRows( |
|
|
source.Bounds(), |
|
|
source.Bounds(), |
|
|
configuration, |
|
|
configuration, |
|
|
rows => |
|
|
new Rotate270RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination)); |
|
|
{ |
|
|
|
|
|
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>
|
|
|
/// <summary>
|
|
|
@ -195,28 +159,131 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
|
/// <param name="configuration">The configuration.</param>
|
|
|
/// <param name="configuration">The configuration.</param>
|
|
|
private void Rotate90(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) |
|
|
private void Rotate90(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) |
|
|
{ |
|
|
{ |
|
|
int width = source.Width; |
|
|
|
|
|
int height = source.Height; |
|
|
|
|
|
Rectangle destinationBounds = destination.Bounds(); |
|
|
|
|
|
|
|
|
|
|
|
ParallelRowIterator.IterateRows( |
|
|
ParallelRowIterator.IterateRows( |
|
|
source.Bounds(), |
|
|
source.Bounds(), |
|
|
configuration, |
|
|
configuration, |
|
|
rows => |
|
|
new Rotate90RowIntervalAction(destination.Bounds(), source.Width, source.Height, source, destination)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private readonly struct Rotate180RowIntervalAction : IRowIntervalAction |
|
|
|
|
|
{ |
|
|
|
|
|
private readonly int width; |
|
|
|
|
|
private readonly int height; |
|
|
|
|
|
private readonly ImageFrame<TPixel> source; |
|
|
|
|
|
private readonly ImageFrame<TPixel> destination; |
|
|
|
|
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
|
|
public Rotate180RowIntervalAction( |
|
|
|
|
|
int width, |
|
|
|
|
|
int height, |
|
|
|
|
|
ImageFrame<TPixel> source, |
|
|
|
|
|
ImageFrame<TPixel> destination) |
|
|
|
|
|
{ |
|
|
|
|
|
this.width = width; |
|
|
|
|
|
this.height = height; |
|
|
|
|
|
this.source = source; |
|
|
|
|
|
this.destination = destination; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
|
|
public void Invoke(in RowInterval rows) |
|
|
|
|
|
{ |
|
|
|
|
|
for (int y = rows.Min; y < rows.Max; y++) |
|
|
|
|
|
{ |
|
|
|
|
|
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y); |
|
|
|
|
|
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); |
|
|
|
|
|
|
|
|
|
|
|
for (int x = 0; x < this.width; x++) |
|
|
|
|
|
{ |
|
|
|
|
|
targetRow[this.width - x - 1] = sourceRow[x]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private readonly struct Rotate270RowIntervalAction : IRowIntervalAction |
|
|
|
|
|
{ |
|
|
|
|
|
private readonly Rectangle bounds; |
|
|
|
|
|
private readonly int width; |
|
|
|
|
|
private readonly int height; |
|
|
|
|
|
private readonly ImageFrame<TPixel> source; |
|
|
|
|
|
private readonly ImageFrame<TPixel> destination; |
|
|
|
|
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
|
|
public Rotate270RowIntervalAction( |
|
|
|
|
|
Rectangle bounds, |
|
|
|
|
|
int width, |
|
|
|
|
|
int height, |
|
|
|
|
|
ImageFrame<TPixel> source, |
|
|
|
|
|
ImageFrame<TPixel> destination) |
|
|
|
|
|
{ |
|
|
|
|
|
this.bounds = bounds; |
|
|
|
|
|
this.width = width; |
|
|
|
|
|
this.height = height; |
|
|
|
|
|
this.source = source; |
|
|
|
|
|
this.destination = destination; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
|
|
public void Invoke(in RowInterval rows) |
|
|
|
|
|
{ |
|
|
|
|
|
for (int y = rows.Min; y < rows.Max; y++) |
|
|
|
|
|
{ |
|
|
|
|
|
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y); |
|
|
|
|
|
for (int x = 0; x < this.width; x++) |
|
|
|
|
|
{ |
|
|
|
|
|
int newX = this.height - y - 1; |
|
|
|
|
|
newX = this.height - newX - 1; |
|
|
|
|
|
int newY = this.width - x - 1; |
|
|
|
|
|
|
|
|
|
|
|
if (this.bounds.Contains(newX, newY)) |
|
|
|
|
|
{ |
|
|
|
|
|
this.destination[newX, newY] = sourceRow[x]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private readonly struct Rotate90RowIntervalAction : IRowIntervalAction |
|
|
|
|
|
{ |
|
|
|
|
|
private readonly Rectangle bounds; |
|
|
|
|
|
private readonly int width; |
|
|
|
|
|
private readonly int height; |
|
|
|
|
|
private readonly ImageFrame<TPixel> source; |
|
|
|
|
|
private readonly ImageFrame<TPixel> destination; |
|
|
|
|
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
|
|
public Rotate90RowIntervalAction( |
|
|
|
|
|
Rectangle bounds, |
|
|
|
|
|
int width, |
|
|
|
|
|
int height, |
|
|
|
|
|
ImageFrame<TPixel> source, |
|
|
|
|
|
ImageFrame<TPixel> destination) |
|
|
|
|
|
{ |
|
|
|
|
|
this.bounds = bounds; |
|
|
|
|
|
this.width = width; |
|
|
|
|
|
this.height = height; |
|
|
|
|
|
this.source = source; |
|
|
|
|
|
this.destination = destination; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
|
|
public void Invoke(in RowInterval rows) |
|
|
|
|
|
{ |
|
|
|
|
|
for (int y = rows.Min; y < rows.Max; y++) |
|
|
|
|
|
{ |
|
|
|
|
|
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y); |
|
|
|
|
|
int newX = this.height - y - 1; |
|
|
|
|
|
for (int x = 0; x < this.width; x++) |
|
|
{ |
|
|
{ |
|
|
for (int y = rows.Min; y < rows.Max; y++) |
|
|
if (this.bounds.Contains(newX, x)) |
|
|
{ |
|
|
{ |
|
|
Span<TPixel> sourceRow = source.GetPixelRowSpan(y); |
|
|
this.destination[newX, x] = sourceRow[x]; |
|
|
int newX = height - y - 1; |
|
|
|
|
|
for (int x = 0; x < width; x++) |
|
|
|
|
|
{ |
|
|
|
|
|
if (destinationBounds.Contains(newX, x)) |
|
|
|
|
|
{ |
|
|
|
|
|
destination[newX, x] = sourceRow[x]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|