// // 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); } } }