//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
namespace ImageSharp.Processing.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
///
/// Provides methods that allow the rotating of images.
///
/// The pixel format.
public class RotateProcessor : Matrix3x2Processor
where TColor : struct, IPackedPixel, IEquatable
{
///
/// The transform matrix to apply.
///
private Matrix3x2 processMatrix;
///
/// Gets or sets the angle of processMatrix in degrees.
///
public float Angle { get; set; }
///
/// Gets or sets a value indicating whether to expand the canvas to fit the rotated image.
///
public bool Expand { get; set; } = true;
///
protected override void OnApply(ImageBase source, Rectangle sourceRectangle)
{
if (this.OptimizedApply(source))
{
return;
}
int height = this.CanvasRectangle.Height;
int width = this.CanvasRectangle.Width;
Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix);
TColor[] target = PixelPool.RentPixels(width * height);
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock(width, height))
{
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
{
for (int x = 0; x < width; x++)
{
Point transformedPoint = Point.Rotate(new Point(x, y), matrix);
if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y))
{
targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y];
}
}
});
}
source.SetPixels(width, height, target);
}
///
protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle)
{
if (Math.Abs(this.Angle) < Constants.Epsilon || Math.Abs(this.Angle - 90) < Constants.Epsilon || Math.Abs(this.Angle - 180) < Constants.Epsilon || Math.Abs(this.Angle - 270) < Constants.Epsilon)
{
return;
}
this.processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle);
if (this.Expand)
{
this.CreateNewCanvas(sourceRectangle, this.processMatrix);
}
}
///
/// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees.
///
/// The source image.
/// The
private bool OptimizedApply(ImageBase source)
{
if (Math.Abs(this.Angle) < Constants.Epsilon)
{
// No need to do anything so return.
return true;
}
if (Math.Abs(this.Angle - 90) < Constants.Epsilon)
{
this.Rotate90(source);
return true;
}
if (Math.Abs(this.Angle - 180) < Constants.Epsilon)
{
this.Rotate180(source);
return true;
}
if (Math.Abs(this.Angle - 270) < Constants.Epsilon)
{
this.Rotate270(source);
return true;
}
return false;
}
///
/// Rotates the image 270 degrees clockwise at the centre point.
///
/// The source image.
private void Rotate270(ImageBase source)
{
int width = source.Width;
int height = source.Height;
TColor[] target = PixelPool.RentPixels(width * height);
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock(height, width))
{
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
{
for (int x = 0; x < width; x++)
{
int newX = height - y - 1;
newX = height - newX - 1;
int newY = width - x - 1;
targetPixels[newX, newY] = sourcePixels[x, y];
}
});
}
source.SetPixels(height, width, target);
}
///
/// Rotates the image 180 degrees clockwise at the centre point.
///
/// The source image.
private void Rotate180(ImageBase source)
{
int width = source.Width;
int height = source.Height;
TColor[] target = PixelPool.RentPixels(width * height);
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock(width, height))
{
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
{
for (int x = 0; x < width; x++)
{
int newX = width - x - 1;
int newY = height - y - 1;
targetPixels[newX, newY] = sourcePixels[x, y];
}
});
}
source.SetPixels(width, height, target);
}
///
/// Rotates the image 90 degrees clockwise at the centre point.
///
/// The source image.
private void Rotate90(ImageBase source)
{
int width = source.Width;
int height = source.Height;
TColor[] target = PixelPool.RentPixels(width * height);
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock(height, width))
{
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
{
for (int x = 0; x < width; x++)
{
int newX = height - y - 1;
targetPixels[newX, x] = sourcePixels[x, y];
}
});
}
source.SetPixels(height, width, target);
}
}
}