mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Adds "inside rotation" filter/method Former-commit-id: 21483ea0ff11b36e63858c49292a3a91f50dee7d Former-commit-id: e9cedf7f10726633e765ce27eac77e55796a17f3af/merge-core
10 changed files with 410 additions and 78 deletions
@ -0,0 +1,54 @@ |
|||
namespace ImageProcessor.UnitTests.Imaging.Helpers |
|||
{ |
|||
using System.Drawing; |
|||
using FluentAssertions; |
|||
using ImageProcessor.Imaging.Helpers; |
|||
using NUnit.Framework; |
|||
|
|||
/// <summary>
|
|||
/// Test harness for the image math unit tests
|
|||
/// </summary>
|
|||
public class ImageMathsUnitTests |
|||
{ |
|||
/// <summary>
|
|||
/// Tests that the bounding rectangle of a rotated image is calculated
|
|||
/// </summary>
|
|||
/// <param name="width">The width of the image.</param>
|
|||
/// <param name="height">The height of the image.</param>
|
|||
/// <param name="angle">The rotation angle.</param>
|
|||
/// <param name="expectedWidth">The expected width.</param>
|
|||
/// <param name="expectedHeight">The expected height.</param>
|
|||
[Test] |
|||
[TestCase(100, 100, 45, 141, 141)] |
|||
[TestCase(100, 100, 30, 137, 137)] |
|||
[TestCase(100, 200, 50, 217, 205)] |
|||
public void BoundingRotatedRectangleIsCalculated(int width, int height, float angle, int expectedWidth, int expectedHeight) |
|||
{ |
|||
Rectangle result = ImageMaths.GetBoundingRotatedRectangle(width, height, angle); |
|||
|
|||
result.Width.Should().Be(expectedWidth, "because the rotated width should have been calculated"); |
|||
result.Height.Should().Be(expectedHeight, "because the rotated height should have been calculated"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests that the zoom needed for an "inside" rotation is calculated
|
|||
/// </summary>
|
|||
/// <param name="imageWidth">Width of the image.</param>
|
|||
/// <param name="imageHeight">Height of the image.</param>
|
|||
/// <param name="angle">The rotation angle.</param>
|
|||
/// <param name="expected">The expected zoom.</param>
|
|||
[Test] |
|||
[TestCase(100, 100, 45, 1.41f)] |
|||
[TestCase(100, 100, 15, 1.22f)] |
|||
[TestCase(100, 200, 45, 2.12f)] |
|||
[TestCase(200, 100, 45, 2.12f)] |
|||
[TestCase(600, 450, 20, 1.39f)] |
|||
[TestCase(600, 450, 45, 1.64f)] |
|||
public void RotationZoomIsCalculated(int imageWidth, int imageHeight, float angle, float expected) |
|||
{ |
|||
float result = ImageMaths.ZoomAfterRotation(imageWidth, imageHeight, angle); |
|||
|
|||
result.Should().BeApproximately(expected, 0.01f, "because the zoom level after rotation should have been calculated"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="ImageLayer.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Encapsulates the properties required to add an rotation layer to an image.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Imaging |
|||
{ |
|||
/// <summary>
|
|||
/// A rotation layer to apply an inside rotation to an image
|
|||
/// </summary>
|
|||
public class RotateInsideLayer |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the rotation angle.
|
|||
/// </summary>
|
|||
public float Angle { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to keep the image dimensions.
|
|||
/// If set to true, the image is zoomed inside the area.
|
|||
/// If set to false, the area is resized to match the rotated image.
|
|||
/// </summary>
|
|||
public bool KeepImageDimensions { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,153 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="Rotate.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Encapsulates methods to rotate the inside of an image.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Processors |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using ImageProcessor.Common.Exceptions; |
|||
using ImageProcessor.Imaging; |
|||
|
|||
/// <summary>
|
|||
/// Encapsulates the methods to rotate the inside of an image
|
|||
/// </summary>
|
|||
public class RotateInside : IGraphicsProcessor |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the DynamicParameter.
|
|||
/// </summary>
|
|||
public dynamic DynamicParameter { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets any additional settings required by the processor.
|
|||
/// </summary>
|
|||
public Dictionary<string, string> Settings { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Processes the image.
|
|||
/// </summary>
|
|||
/// <param name="factory">The current instance of the <see cref="T:ImageProcessor.ImageFactory" /> class containing
|
|||
/// the image to process.</param>
|
|||
/// <returns>
|
|||
/// The processed image from the current instance of the <see cref="T:ImageProcessor.ImageFactory" /> class.
|
|||
/// </returns>
|
|||
/// <remarks>
|
|||
/// Based on <see href="http://math.stackexchange.com/questions/1070853/"/>
|
|||
/// </remarks>
|
|||
public Image ProcessImage(ImageFactory factory) |
|||
{ |
|||
Bitmap newImage = null; |
|||
Image image = factory.Image; |
|||
|
|||
try |
|||
{ |
|||
RotateInsideLayer rotateLayer = this.DynamicParameter; |
|||
|
|||
// Create a rotated image.
|
|||
newImage = this.RotateImage(image, rotateLayer); |
|||
|
|||
image.Dispose(); |
|||
image = newImage; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
if (newImage != null) |
|||
{ |
|||
newImage.Dispose(); |
|||
} |
|||
|
|||
throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex); |
|||
} |
|||
|
|||
return image; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates the inside of an image to the given angle at the given position.
|
|||
/// </summary>
|
|||
/// <param name="image">The image to rotate</param>
|
|||
/// <param name="rotateLayer">The rotation layer.</param>
|
|||
/// <remarks>
|
|||
/// Based on the Rotate effect
|
|||
/// </remarks>
|
|||
/// <returns>The image rotated to the given angle at the given position.</returns>
|
|||
private Bitmap RotateImage(Image image, RotateInsideLayer rotateLayer) |
|||
{ |
|||
Size newSize = new Size(image.Width, image.Height); |
|||
|
|||
float zoom = Imaging.Helpers.ImageMaths.ZoomAfterRotation(image.Width, image.Height, rotateLayer.Angle); |
|||
|
|||
// if we don't keep the image dimensions, calculate the new ones
|
|||
if (!rotateLayer.KeepImageDimensions) |
|||
{ |
|||
newSize.Width = (int)(newSize.Width / zoom); |
|||
newSize.Height = (int)(newSize.Height / zoom); |
|||
} |
|||
|
|||
// Center of the image
|
|||
float rotateAtX = Math.Abs(image.Width / 2); |
|||
float rotateAtY = Math.Abs(image.Height / 2); |
|||
|
|||
// Create a new empty bitmap to hold rotated image
|
|||
Bitmap newImage = new Bitmap(newSize.Width, newSize.Height); |
|||
newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); |
|||
|
|||
// Make a graphics object from the empty bitmap
|
|||
using (Graphics graphics = Graphics.FromImage(newImage)) |
|||
{ |
|||
// Reduce the jagged edge.
|
|||
graphics.SmoothingMode = SmoothingMode.AntiAlias; |
|||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; |
|||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; |
|||
graphics.CompositingQuality = CompositingQuality.HighQuality; |
|||
|
|||
if (rotateLayer.KeepImageDimensions) |
|||
{ |
|||
// Put the rotation point in the "center" of the image
|
|||
graphics.TranslateTransform(rotateAtX, rotateAtY); |
|||
|
|||
// Rotate the image
|
|||
graphics.RotateTransform(rotateLayer.Angle); |
|||
|
|||
// Zooms the image to fit the area
|
|||
graphics.ScaleTransform(zoom, zoom); |
|||
|
|||
// Move the image back
|
|||
graphics.TranslateTransform(-rotateAtX, -rotateAtY); |
|||
|
|||
// Draw passed in image onto graphics object
|
|||
graphics.DrawImage(image, new PointF(0, 0)); |
|||
} |
|||
else |
|||
{ |
|||
// calculate the difference between the center of the original image and the center of the new image
|
|||
int diffX = (image.Width - newSize.Width) / 2; |
|||
int diffY = (image.Height - newSize.Height) / 2; |
|||
|
|||
// Put the rotation point in the "center" of the old image
|
|||
graphics.TranslateTransform(rotateAtX - diffX, rotateAtY - diffY); |
|||
|
|||
// Rotate the image
|
|||
graphics.RotateTransform(rotateLayer.Angle); |
|||
|
|||
// Move the image back
|
|||
graphics.TranslateTransform(-(rotateAtX - diffX), -(rotateAtY - diffY)); |
|||
|
|||
// Draw passed in image onto graphics object
|
|||
graphics.DrawImage(image, new PointF(-diffX, -diffY)); |
|||
} |
|||
} |
|||
|
|||
return newImage; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue