Browse Source

Finished refactoring transforms

pull/904/head
Anton Firszov 8 years ago
parent
commit
0fbf85ff2f
  1. 15
      src/ImageSharp/Processing/CropExtensions.cs
  2. 19
      src/ImageSharp/Processing/EntropyCropExtensions.cs
  3. 9
      src/ImageSharp/Processing/FlipExtensions.cs
  4. 2
      src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
  5. 106
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  6. 127
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
  7. 92
      src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs
  8. 103
      src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs
  9. 59
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
  10. 75
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
  11. 46
      src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs
  12. 59
      src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
  13. 76
      src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs
  14. 87
      src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs
  15. 109
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  16. 126
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
  17. 207
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  18. 215
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs
  19. 10
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  20. 2
      src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs
  21. 2
      src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs
  22. 24
      src/ImageSharp/Processing/RotateExtensions.cs
  23. 6
      src/ImageSharp/Processing/RotateFlipExtensions.cs
  24. 24
      src/ImageSharp/Processing/SkewExtensions.cs
  25. 97
      src/ImageSharp/Processing/TransformExtensions.cs
  26. 3
      tests/ImageSharp.Tests/Drawing/DrawImageTest.cs
  27. 4
      tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs
  28. 2
      tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs
  29. 2
      tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs
  30. 8
      tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs
  31. 4
      tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs
  32. 4
      tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs
  33. 2
      tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs
  34. 2
      tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs

15
src/ImageSharp/Processing/CropExtensions.cs

@ -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()));
} }
} }

19
src/ImageSharp/Processing/EntropyCropExtensions.cs

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

9
src/ImageSharp/Processing/FlipExtensions.cs

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

2
src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs

@ -1,5 +1,5 @@
// 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 System;
using System.Numerics; using System.Numerics;

106
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs

@ -1,26 +1,20 @@
// 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 System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// Provides the base methods to perform affine transforms on an image. /// Defines an affine transformation applicable on an <see cref="Image"/>.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public class AffineTransformProcessor : IImageProcessor
internal class AffineTransformProcessor<TPixel> : TransformProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="AffineTransformProcessor"/> class.
/// </summary> /// </summary>
/// <param name="matrix">The transform matrix.</param> /// <param name="matrix">The transform matrix.</param>
/// <param name="sampler">The sampler to perform the transform operation.</param> /// <param name="sampler">The sampler to perform the transform operation.</param>
@ -48,95 +42,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
public Size TargetDimensions { get; } public Size TargetDimensions { get; }
/// <inheritdoc/> /// <inheritdoc />
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle) public virtual IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>()
where TPixel : struct, IPixel<TPixel>
{ {
// We will always be creating the clone even for mutate because we may need to resize the canvas return new AffineTransformProcessor<TPixel>(this);
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);
if (this.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(), this.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();
}
} }
} }
} }

127
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs

@ -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();
}
}
}
}

92
src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs

@ -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;
} }
} }
} }

103
src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs

@ -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;
}
}
}

59
src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs

@ -1,32 +1,28 @@
// 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 System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// Provides methods to allow the cropping of an image. /// Defines a crop operation on an image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public sealed class CropProcessor : IImageProcessor
internal class CropProcessor<TPixel> : TransformProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CropProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="CropProcessor"/> class.
/// </summary> /// </summary>
/// <param name="cropRectangle">The target cropped rectangle.</param> /// <param name="cropRectangle">The target cropped rectangle.</param>
/// <param name="sourceSize">The source image size.</param> /// <param name="sourceSize">The source image size.</param>
public CropProcessor(Rectangle cropRectangle, Size sourceSize) public CropProcessor(Rectangle cropRectangle, Size sourceSize)
{ {
// Check bounds here and throw if we are passed a rectangle exceeding our source bounds. // Check bounds here and throw if we are passed a rectangle exceeding our source bounds.
Guard.IsTrue(new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), nameof(cropRectangle), "Crop rectangle should be smaller than the source bounds."); Guard.IsTrue(
new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle),
nameof(cropRectangle),
"Crop rectangle should be smaller than the source bounds.");
this.CropRectangle = cropRectangle; this.CropRectangle = cropRectangle;
} }
@ -35,44 +31,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
public Rectangle CropRectangle { get; } public Rectangle CropRectangle { get; }
/// <inheritdoc/> /// <inheritdoc />
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle) public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>()
where TPixel : struct, IPixel<TPixel>
{ {
// We will always be creating the clone even for mutate because we may need to resize the canvas return new CropProcessor<TPixel>(this);
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);
}
});
} }
} }
} }

75
src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs

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

46
src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs

@ -1,31 +1,25 @@
// 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.Advanced;
using SixLabors.ImageSharp.PixelFormats; 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 namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// Provides methods to allow the cropping of an image to preserve areas of highest entropy. /// Defines cropping operation that preserves areas of highest entropy.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public sealed class EntropyCropProcessor : IImageProcessor
internal class EntropyCropProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EntropyCropProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="EntropyCropProcessor"/> class.
/// </summary> /// </summary>
public EntropyCropProcessor() public EntropyCropProcessor()
: this(.5F) : this(.5F)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EntropyCropProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="EntropyCropProcessor"/> class.
/// </summary> /// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param> /// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <exception cref="System.ArgumentException"> /// <exception cref="System.ArgumentException">
@ -42,33 +36,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
public float Threshold { get; } public float Threshold { get; }
/// <inheritdoc/> /// <inheritdoc />
protected override void BeforeImageApply(Image<TPixel> source, Rectangle sourceRectangle) public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>()
where TPixel : struct, IPixel<TPixel>
{ {
Rectangle rectangle; return new EntropyCropProcessor<TPixel>(this);
// 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.Threshold).Apply(temp, sourceRectangle, configuration);
// Search for the first white pixels
rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0);
}
new CropProcessor<TPixel>(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();
} }
} }
} }

59
src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs

@ -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();
}
}
}

76
src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs

@ -1,27 +1,17 @@
// 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 System.Buffers;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// Provides methods that allow the flipping of an image around its center point. /// Defines a flipping around the center point of the image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public class FlipProcessor : IImageProcessor
internal class FlipProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FlipProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="FlipProcessor"/> class.
/// </summary> /// </summary>
/// <param name="flipMode">The <see cref="FlipMode"/> used to perform flipping.</param> /// <param name="flipMode">The <see cref="FlipMode"/> used to perform flipping.</param>
public FlipProcessor(FlipMode flipMode) public FlipProcessor(FlipMode flipMode)
@ -34,63 +24,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
public FlipMode FlipMode { get; } public FlipMode FlipMode { get; }
/// <inheritdoc/> /// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>()
where TPixel : struct, IPixel<TPixel>
{ {
switch (this.FlipMode) return new FlipProcessor<TPixel>(this);
{
// 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();
}
});
} }
} }
} }

87
src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs

@ -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();
}
});
}
}
}

109
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs

@ -1,26 +1,20 @@
// 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 System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// Provides the base methods to perform non-affine transforms on an image. /// Defines a projective transformation applicable to an <see cref="Image"/>.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public sealed class ProjectiveTransformProcessor : IImageProcessor
internal class ProjectiveTransformProcessor<TPixel> : TransformProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="ProjectiveTransformProcessor"/> class.
/// </summary> /// </summary>
/// <param name="matrix">The transform matrix.</param> /// <param name="matrix">The transform matrix.</param>
/// <param name="sampler">The sampler to perform the transform operation.</param> /// <param name="sampler">The sampler to perform the transform operation.</param>
@ -39,103 +33,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public IResampler Sampler { get; } public IResampler Sampler { get; }
/// <summary> /// <summary>
/// Gets the matrix used to supply the projective transform /// Gets the matrix used to supply the projective transform.
/// </summary> /// </summary>
public Matrix4x4 TransformMatrix { get; } public Matrix4x4 TransformMatrix { get; }
/// <summary> /// <summary>
/// Gets the target dimensions to constrain the transformed image to /// Gets the target dimensions to constrain the transformed image to.
/// </summary> /// </summary>
public Size TargetDimensions { get; } public Size TargetDimensions { get; }
/// <inheritdoc/> /// <inheritdoc />
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle) public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>()
where TPixel : struct, IPixel<TPixel>
{ {
// We will always be creating the clone even for mutate because we may need to resize the canvas return new ProjectiveTransformProcessor<TPixel>(this);
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)
{
// Handle tranforms that result in output identical to the original.
if (this.TransformMatrix.Equals(default) || this.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(this.TransformMatrix, out Matrix4x4 matrix);
if (this.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(), this.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();
}
} }
} }
} }

126
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs

@ -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();
}
}
}
}

207
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs

@ -1,26 +1,19 @@
// 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 System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// Provides methods that allow the rotating of images. /// Defines a rotation applicable to an <see cref="Image"/>.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public sealed class RotateProcessor : AffineTransformProcessor
internal class RotateProcessor<TPixel> : AffineTransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="RotateProcessor"/> class.
/// </summary> /// </summary>
/// <param name="degrees">The angle of rotation in degrees.</param> /// <param name="degrees">The angle of rotation in degrees.</param>
/// <param name="sourceSize">The source image size</param> /// <param name="sourceSize">The source image size</param>
@ -30,16 +23,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="RotateProcessor"/> class.
/// </summary> /// </summary>
/// <param name="degrees">The angle of rotation in degrees.</param> /// <param name="degrees">The angle of rotation in degrees.</param>
/// <param name="sampler">The sampler to perform the rotating operation.</param> /// <param name="sampler">The sampler to perform the rotating operation.</param>
/// <param name="sourceSize">The source image size</param> /// <param name="sourceSize">The source image size</param>
public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) public RotateProcessor(float degrees, IResampler sampler, Size sourceSize)
: this( : this(
TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize), TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize),
sampler, sampler,
sourceSize) sourceSize)
=> this.Degrees = degrees; => this.Degrees = degrees;
// Helper constructor // Helper constructor
@ -53,190 +46,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
public float Degrees { get; } public float Degrees { get; }
/// <inheritdoc/> /// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration) public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>()
{ {
if (this.OptimizedApply(source, destination, configuration)) return new RotateProcessor<TPixel>(this);
{
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];
}
}
}
});
} }
} }
} }

215
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs

@ -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];
}
}
}
});
}
}
}

10
src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs

@ -9,14 +9,12 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// Provides methods that allow the skewing of images. /// Defines a skew transformation applicable to an <see cref="Image"/>.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> internal class SkewProcessor : AffineTransformProcessor
internal class SkewProcessor<TPixel> : AffineTransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SkewProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="SkewProcessor"/> class.
/// </summary> /// </summary>
/// <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>
@ -27,7 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SkewProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="SkewProcessor"/> class.
/// </summary> /// </summary>
/// <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>

2
src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs

@ -16,6 +16,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <inheritdoc/> /// <inheritdoc/>
protected override void AfterImageApply(Image<TPixel> source, Image<TPixel> destination, Rectangle sourceRectangle) protected override void AfterImageApply(Image<TPixel> source, Image<TPixel> destination, Rectangle sourceRectangle)
=> TransformProcessorHelpers.UpdateDimensionalMetData(destination); => TransformProcessorHelpers.UpdateDimensionalMetadata(destination);
} }
} }

2
src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image to update</param> /// <param name="image">The image to update</param>
public static void UpdateDimensionalMetData<TPixel>(Image<TPixel> image) public static void UpdateDimensionalMetadata<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
ExifProfile profile = image.Metadata.ExifProfile; ExifProfile profile = image.Metadata.ExifProfile;

24
src/ImageSharp/Processing/RotateExtensions.cs

@ -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()));
} }
} }

6
src/ImageSharp/Processing/RotateFlipExtensions.cs

@ -6,20 +6,18 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing namespace SixLabors.ImageSharp.Processing
{ {
/// <summary> /// <summary>
/// Adds extensions that allow the application of rotate-flip operations to the <see cref="Image{TPixel}"/> type. /// Adds extensions that allow the application of rotate-flip operations to the <see cref="Image"/> type.
/// </summary> /// </summary>
public static class RotateFlipExtensions public static class RotateFlipExtensions
{ {
/// <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, flip, or both.</param> /// <param name="source">The image to rotate, flip, or both.</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>
/// <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> RotateFlip<TPixel>(this IImageProcessingContext<TPixel> source, RotateMode rotateMode, FlipMode flipMode) public static IImageProcessingContext RotateFlip(this IImageProcessingContext source, RotateMode rotateMode, FlipMode flipMode)
where TPixel : struct, IPixel<TPixel>
=> source.Rotate(rotateMode).Flip(flipMode); => source.Rotate(rotateMode).Flip(flipMode);
} }
} }

24
src/ImageSharp/Processing/SkewExtensions.cs

@ -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()));
} }
} }

97
src/ImageSharp/Processing/TransformExtensions.cs

@ -3,60 +3,53 @@
using System.Numerics; using System.Numerics;
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 composable transform operations to the <see cref="Image{TPixel}"/> type. /// Adds extensions that allow the application of composable transform operations to the <see cref="Image"/> type.
/// </summary> /// </summary>
public static class TransformExtensions public static class TransformExtensions
{ {
/// <summary> /// <summary>
/// Performs an affine transform of an image. /// Performs an affine transform of an image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to transform.</param> /// <param name="source">The image to transform.</param>
/// <param name="builder">The affine transform builder.</param> /// <param name="builder">The affine transform builder.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Transform<TPixel>( public static IImageProcessingContext Transform(
this IImageProcessingContext<TPixel> source, this IImageProcessingContext source,
AffineTransformBuilder builder) AffineTransformBuilder builder) =>
where TPixel : struct, IPixel<TPixel> Transform(source, builder, KnownResamplers.Bicubic);
=> Transform(source, builder, KnownResamplers.Bicubic);
/// <summary> /// <summary>
/// Performs an affine transform of an image using the specified sampling algorithm. /// Performs an affine transform of an image using the specified sampling algorithm.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="ctx">The <see cref="IImageProcessingContext"/>.</param>
/// <param name="ctx">The <see cref="IImageProcessingContext{TPixel}"/>.</param>
/// <param name="builder">The affine transform builder.</param> /// <param name="builder">The affine transform builder.</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> Transform<TPixel>( public static IImageProcessingContext Transform(
this IImageProcessingContext<TPixel> ctx, this IImageProcessingContext ctx,
AffineTransformBuilder builder, AffineTransformBuilder builder,
IResampler sampler) IResampler sampler) =>
where TPixel : struct, IPixel<TPixel> ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler);
=> ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler);
/// <summary> /// <summary>
/// Performs an affine transform of an image using the specified sampling algorithm. /// Performs an affine transform of an image using the specified sampling algorithm.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="ctx">The <see cref="IImageProcessingContext"/>.</param>
/// <param name="ctx">The <see cref="IImageProcessingContext{TPixel}"/>.</param>
/// <param name="sourceRectangle">The source rectangle</param> /// <param name="sourceRectangle">The source rectangle</param>
/// <param name="builder">The affine transform builder.</param> /// <param name="builder">The affine transform builder.</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> Transform<TPixel>( public static IImageProcessingContext Transform(
this IImageProcessingContext<TPixel> ctx, this IImageProcessingContext ctx,
Rectangle sourceRectangle, Rectangle sourceRectangle,
AffineTransformBuilder builder, AffineTransformBuilder builder,
IResampler sampler) IResampler sampler)
where TPixel : struct, IPixel<TPixel>
{ {
Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); Matrix3x2 transform = builder.BuildMatrix(sourceRectangle);
Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform);
@ -66,69 +59,61 @@ namespace SixLabors.ImageSharp.Processing
/// <summary> /// <summary>
/// Performs an affine transform of an image using the specified sampling algorithm. /// Performs an affine transform of an image using the specified sampling algorithm.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="ctx">The <see cref="IImageProcessingContext"/>.</param>
/// <param name="ctx">The <see cref="IImageProcessingContext{TPixel}"/>.</param>
/// <param name="sourceRectangle">The source rectangle</param> /// <param name="sourceRectangle">The source rectangle</param>
/// <param name="transform">The transformation matrix.</param> /// <param name="transform">The transformation matrix.</param>
/// <param name="targetDimensions">The size of the result image.</param> /// <param name="targetDimensions">The size of the result image.</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> Transform<TPixel>( public static IImageProcessingContext Transform(
this IImageProcessingContext<TPixel> ctx, this IImageProcessingContext ctx,
Rectangle sourceRectangle, Rectangle sourceRectangle,
Matrix3x2 transform, Matrix3x2 transform,
Size targetDimensions, Size targetDimensions,
IResampler sampler) IResampler sampler)
where TPixel : struct, IPixel<TPixel>
{ {
return ctx.ApplyProcessor( return ctx.ApplyProcessor(
new AffineTransformProcessor<TPixel>(transform, sampler, targetDimensions), new AffineTransformProcessor(transform, sampler, targetDimensions),
sourceRectangle); sourceRectangle);
} }
/// <summary> /// <summary>
/// Performs a projective transform of an image. /// Performs a projective transform of an image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to transform.</param> /// <param name="source">The image to transform.</param>
/// <param name="builder">The affine transform builder.</param> /// <param name="builder">The affine transform builder.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Transform<TPixel>( public static IImageProcessingContext Transform(
this IImageProcessingContext<TPixel> source, this IImageProcessingContext source,
ProjectiveTransformBuilder builder) ProjectiveTransformBuilder builder) =>
where TPixel : struct, IPixel<TPixel> Transform(source, builder, KnownResamplers.Bicubic);
=> Transform(source, builder, KnownResamplers.Bicubic);
/// <summary> /// <summary>
/// Performs a projective transform of an image using the specified sampling algorithm. /// Performs a projective transform of an image using the specified sampling algorithm.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="ctx">The <see cref="IImageProcessingContext"/>.</param>
/// <param name="ctx">The <see cref="IImageProcessingContext{TPixel}"/>.</param>
/// <param name="builder">The projective transform builder.</param> /// <param name="builder">The projective transform builder.</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> Transform<TPixel>( public static IImageProcessingContext Transform(
this IImageProcessingContext<TPixel> ctx, this IImageProcessingContext ctx,
ProjectiveTransformBuilder builder, ProjectiveTransformBuilder builder,
IResampler sampler) IResampler sampler) =>
where TPixel : struct, IPixel<TPixel> ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler);
=> ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler);
/// <summary> /// <summary>
/// Performs a projective transform of an image using the specified sampling algorithm. /// Performs a projective transform of an image using the specified sampling algorithm.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="ctx">The <see cref="IImageProcessingContext"/>.</param>
/// <param name="ctx">The <see cref="IImageProcessingContext{TPixel}"/>.</param>
/// <param name="sourceRectangle">The source rectangle</param> /// <param name="sourceRectangle">The source rectangle</param>
/// <param name="builder">The projective transform builder.</param> /// <param name="builder">The projective transform builder.</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> Transform<TPixel>( public static IImageProcessingContext Transform(
this IImageProcessingContext<TPixel> ctx, this IImageProcessingContext ctx,
Rectangle sourceRectangle, Rectangle sourceRectangle,
ProjectiveTransformBuilder builder, ProjectiveTransformBuilder builder,
IResampler sampler) IResampler sampler)
where TPixel : struct, IPixel<TPixel>
{ {
Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); Matrix4x4 transform = builder.BuildMatrix(sourceRectangle);
Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform);
@ -138,23 +123,21 @@ namespace SixLabors.ImageSharp.Processing
/// <summary> /// <summary>
/// Performs a projective transform of an image using the specified sampling algorithm. /// Performs a projective transform of an image using the specified sampling algorithm.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="ctx">The <see cref="IImageProcessingContext"/>.</param>
/// <param name="ctx">The <see cref="IImageProcessingContext{TPixel}"/>.</param>
/// <param name="sourceRectangle">The source rectangle</param> /// <param name="sourceRectangle">The source rectangle</param>
/// <param name="transform">The transformation matrix.</param> /// <param name="transform">The transformation matrix.</param>
/// <param name="targetDimensions">The size of the result image.</param> /// <param name="targetDimensions">The size of the result image.</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> Transform<TPixel>( public static IImageProcessingContext Transform(
this IImageProcessingContext<TPixel> ctx, this IImageProcessingContext ctx,
Rectangle sourceRectangle, Rectangle sourceRectangle,
Matrix4x4 transform, Matrix4x4 transform,
Size targetDimensions, Size targetDimensions,
IResampler sampler) IResampler sampler)
where TPixel : struct, IPixel<TPixel>
{ {
return ctx.ApplyProcessor( return ctx.ApplyProcessor(
new ProjectiveTransformProcessor<TPixel>(transform, sampler, targetDimensions), new ProjectiveTransformProcessor(transform, sampler, targetDimensions),
sourceRectangle); sourceRectangle);
} }
} }

3
tests/ImageSharp.Tests/Drawing/DrawImageTest.cs

@ -79,7 +79,8 @@ namespace SixLabors.ImageSharp.Tests
.AppendTranslation(new PointF(10, 10)); .AppendTranslation(new PointF(10, 10));
// Apply a background color so we can see the translation. // Apply a background color so we can see the translation.
blend.Mutate(x => x.Transform(builder).BackgroundColor(NamedColors<TPixel>.HotPink)); blend.Mutate(x => x.Transform(builder));
blend.Mutate(x => x.BackgroundColor(NamedColors<TPixel>.HotPink));
// Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor
var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2);

4
tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public void CropWidthHeightCropProcessorWithRectangleSet(int width, int height) public void CropWidthHeightCropProcessorWithRectangleSet(int width, int height)
{ {
this.operations.Crop(width, height); this.operations.Crop(width, height);
CropProcessor<Rgba32> processor = this.Verify<CropProcessor<Rgba32>>(); CropProcessor processor = this.Verify<CropProcessor>();
Assert.Equal(new Rectangle(0, 0, width, height), processor.CropRectangle); Assert.Equal(new Rectangle(0, 0, width, height), processor.CropRectangle);
} }
@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{ {
var cropRectangle = new Rectangle(x, y, width, height); var cropRectangle = new Rectangle(x, y, width, height);
this.operations.Crop(cropRectangle); this.operations.Crop(cropRectangle);
CropProcessor<Rgba32> processor = this.Verify<CropProcessor<Rgba32>>(); CropProcessor processor = this.Verify<CropProcessor>();
Assert.Equal(cropRectangle, processor.CropRectangle); Assert.Equal(cropRectangle, processor.CropRectangle);
} }

2
tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public void EntropyCropThresholdFloatEntropyCropProcessorWithThreshold(float threshold) public void EntropyCropThresholdFloatEntropyCropProcessorWithThreshold(float threshold)
{ {
this.operations.EntropyCrop(threshold); this.operations.EntropyCrop(threshold);
EntropyCropProcessor<Rgba32> processor = this.Verify<EntropyCropProcessor<Rgba32>>(); EntropyCropProcessor processor = this.Verify<EntropyCropProcessor>();
Assert.Equal(threshold, processor.Threshold); Assert.Equal(threshold, processor.Threshold);
} }

2
tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public void Flip_degreesFloat_RotateProcessorWithAnglesSetAndExpandTrue(FlipMode flip) public void Flip_degreesFloat_RotateProcessorWithAnglesSetAndExpandTrue(FlipMode flip)
{ {
this.operations.Flip(flip); this.operations.Flip(flip);
FlipProcessor<Rgba32> flipProcessor = this.Verify<FlipProcessor<Rgba32>>(); FlipProcessor flipProcessor = this.Verify<FlipProcessor>();
Assert.Equal(flip, flipProcessor.FlipMode); Assert.Equal(flip, flipProcessor.FlipMode);
} }

8
tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs

@ -1,15 +1,13 @@
// 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;
using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Processing.Processors.Transforms;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Transforms namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{ {
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
public class PadTest : BaseImageOperationsExtensionTest public class PadTest : BaseImageOperationsExtensionTest
{ {
[Fact] [Fact]
@ -20,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
IResampler sampler = KnownResamplers.NearestNeighbor; IResampler sampler = KnownResamplers.NearestNeighbor;
this.operations.Pad(width, height); this.operations.Pad(width, height);
ResizeProcessor<Rgba32> resizeProcessor = this.Verify<ResizeProcessor<Rgba32>>(); ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.Width); Assert.Equal(width, resizeProcessor.Width);
Assert.Equal(height, resizeProcessor.Height); Assert.Equal(height, resizeProcessor.Height);

4
tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs

@ -26,8 +26,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public void RotateDegreesFloatRotateProcessorWithAnglesSet(RotateMode angle, FlipMode flip, float expectedAngle) public void RotateDegreesFloatRotateProcessorWithAnglesSet(RotateMode angle, FlipMode flip, float expectedAngle)
{ {
this.operations.RotateFlip(angle, flip); this.operations.RotateFlip(angle, flip);
RotateProcessor<Rgba32> rotateProcessor = this.Verify<RotateProcessor<Rgba32>>(0); RotateProcessor rotateProcessor = this.Verify<RotateProcessor>(0);
FlipProcessor<Rgba32> flipProcessor = this.Verify<FlipProcessor<Rgba32>>(1); FlipProcessor flipProcessor = this.Verify<FlipProcessor>(1);
Assert.Equal(expectedAngle, rotateProcessor.Degrees); Assert.Equal(expectedAngle, rotateProcessor.Degrees);
Assert.Equal(flip, flipProcessor.FlipMode); Assert.Equal(flip, flipProcessor.FlipMode);

4
tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public void RotateDegreesFloatRotateProcessorWithAnglesSet(float angle) public void RotateDegreesFloatRotateProcessorWithAnglesSet(float angle)
{ {
this.operations.Rotate(angle); this.operations.Rotate(angle);
RotateProcessor<Rgba32> processor = this.Verify<RotateProcessor<Rgba32>>(); RotateProcessor processor = this.Verify<RotateProcessor>();
Assert.Equal(angle, processor.Degrees); Assert.Equal(angle, processor.Degrees);
} }
@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public void RotateRotateTypeRotateProcessorWithAnglesConvertedFromEnum(RotateMode angle, float expectedAngle) public void RotateRotateTypeRotateProcessorWithAnglesConvertedFromEnum(RotateMode angle, float expectedAngle)
{ {
this.operations.Rotate(angle); // is this api needed ??? this.operations.Rotate(angle); // is this api needed ???
RotateProcessor<Rgba32> processor = this.Verify<RotateProcessor<Rgba32>>(); RotateProcessor processor = this.Verify<RotateProcessor>();
Assert.Equal(expectedAngle, processor.Degrees); Assert.Equal(expectedAngle, processor.Degrees);
} }

2
tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public void SkewXYCreateSkewProcessorWithAnglesSet() public void SkewXYCreateSkewProcessorWithAnglesSet()
{ {
this.operations.Skew(10, 20); this.operations.Skew(10, 20);
SkewProcessor<Rgba32> processor = this.Verify<SkewProcessor<Rgba32>>(); SkewProcessor processor = this.Verify<SkewProcessor>();
Assert.Equal(10, processor.DegreesX); Assert.Equal(10, processor.DegreesX);
Assert.Equal(20, processor.DegreesY); Assert.Equal(20, processor.DegreesY);

2
tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType); Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType);
Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType); Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType);
TransformProcessorHelpers.UpdateDimensionalMetData(img); TransformProcessorHelpers.UpdateDimensionalMetadata(img);
Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelXDimension).DataType); Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelXDimension).DataType);
Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelYDimension).DataType); Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelYDimension).DataType);

Loading…
Cancel
Save