Browse Source

Make resize and transform processors immutable #473

af/merge-core
James Jackson-South 8 years ago
parent
commit
9e51967c98
  1. 5
      src/ImageSharp/DefaultInternalImageProcessorContext.cs
  2. 8
      src/ImageSharp/IImageProcessingContext{TPixel}.cs
  3. 55
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  4. 12
      src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs
  5. 10
      src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs
  6. 10
      src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs
  7. 1
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
  8. 55
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  9. 164
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
  10. 205
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  11. 10
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  12. 10
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  13. 119
      src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs
  14. 2
      src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs
  15. 120
      src/ImageSharp/Processing/Transforms/Resize.cs
  16. 2
      src/ImageSharp/Processing/Transforms/Rotate.cs
  17. 2
      src/ImageSharp/Processing/Transforms/Skew.cs
  18. 4
      src/ImageSharp/Processing/Transforms/Transform.cs
  19. 14
      tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs
  20. 42
      tests/ImageSharp.Tests/FakeImageOperationsProvider.cs
  21. 2
      tests/ImageSharp.Tests/ImageOperationTests.cs
  22. 7
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs

5
src/ImageSharp/DefaultInternalImageProcessorContext.cs

@ -46,6 +46,9 @@ namespace SixLabors.ImageSharp
return this.destination;
}
/// <inheritdoc/>
public Rectangle Bounds() => this.source.Bounds();
/// <inheritdoc/>
public IImageProcessingContext<TPixel> ApplyProcessor(IImageProcessor<TPixel> processor, Rectangle rectangle)
{
@ -70,7 +73,7 @@ namespace SixLabors.ImageSharp
/// <inheritdoc/>
public IImageProcessingContext<TPixel> ApplyProcessor(IImageProcessor<TPixel> processor)
{
return this.ApplyProcessor(processor, this.source.Bounds());
return this.ApplyProcessor(processor, this.Bounds());
}
}
}

8
src/ImageSharp/IImageProcessingContext{TPixel}.cs

@ -14,6 +14,12 @@ namespace SixLabors.ImageSharp
public interface IImageProcessingContext<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Gets the source image bounds
/// </summary>
/// <returns>The <see cref="Rectangle"/></returns>
Rectangle Bounds();
/// <summary>
/// Adds the processor to the current set of image operations to be applied.
/// </summary>
@ -40,7 +46,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Adds the processors to the current image
/// </summary>
/// <returns>The current image or a new image depending on withere this is alloed to mutate the source image.</returns>
/// <returns>The current image or a new image depending on whether this is allowed to mutate the source image.</returns>
Image<TPixel> Apply();
}
}

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

@ -21,27 +21,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
internal class AffineTransformProcessor<TPixel> : InterpolatedTransformProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private Size targetDimensions;
/// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
public AffineTransformProcessor(Matrix3x2 matrix)
: this(matrix, KnownResamplers.Bicubic)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler)
: this(matrix, sampler, Size.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
/// </summary>
@ -52,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
: base(sampler)
{
this.TransformMatrix = matrix;
this.targetDimensions = targetDimensions;
this.TargetDimensions = targetDimensions;
}
/// <summary>
@ -60,18 +39,17 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
public Matrix3x2 TransformMatrix { get; }
/// <summary>
/// Gets the target dimensions to constrain the transformed image to
/// </summary>
public Size TargetDimensions { get; }
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
if (this.targetDimensions == Size.Empty)
{
// TODO: CreateDestination() should not modify the processors state! (kinda CQRS)
this.targetDimensions = this.GetTransformedDimensions(sourceRectangle.Size, this.TransformMatrix);
}
// 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>(this.targetDimensions, x.MetaData.Clone()));
source.Frames.Select(x => new ImageFrame<TPixel>(this.TargetDimensions, x.MetaData.Clone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.Clone(), frames);
@ -84,8 +62,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
Rectangle sourceRectangle,
Configuration configuration)
{
int height = this.targetDimensions.Height;
int width = this.targetDimensions.Width;
int height = this.TargetDimensions.Height;
int width = this.TargetDimensions.Width;
Rectangle sourceBounds = source.Bounds();
var targetBounds = new Rectangle(0, 0, width, height);
@ -205,8 +183,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
var vector = source[i, j].ToVector4();
// Values are first premultiplied to prevent darkening of edge pixels
Vector4 mupltiplied = vector.Premultiply();
sum += mupltiplied * xWeight * yWeight;
Vector4 multiplied = vector.Premultiply();
sum += multiplied * xWeight * yWeight;
}
}
@ -231,16 +209,5 @@ namespace SixLabors.ImageSharp.Processing.Processors
{
return this.TransformMatrix;
}
/// <summary>
/// Gets the bounding <see cref="Rectangle"/> relative to the source for the given transformation matrix.
/// </summary>
/// <param name="sourceDimensions">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Rectangle"/></returns>
protected virtual Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix)
{
return sourceDimensions;
}
}
}

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

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected override void BeforeImageApply(Image<TPixel> source, Rectangle sourceRectangle)
{
Orientation orientation = GetExifOrientation(source);
Size size = sourceRectangle.Size;
switch (orientation)
{
case Orientation.TopRight:
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
break;
case Orientation.BottomRight:
new RotateProcessor<TPixel>((int)RotateType.Rotate180).Apply(source, sourceRectangle);
new RotateProcessor<TPixel>((int)RotateType.Rotate180, size).Apply(source, sourceRectangle);
break;
case Orientation.BottomLeft:
@ -35,21 +35,21 @@ namespace SixLabors.ImageSharp.Processing.Processors
break;
case Orientation.LeftTop:
new RotateProcessor<TPixel>((int)RotateType.Rotate90).Apply(source, sourceRectangle);
new RotateProcessor<TPixel>((int)RotateType.Rotate90, size).Apply(source, sourceRectangle);
new FlipProcessor<TPixel>(FlipType.Horizontal).Apply(source, sourceRectangle);
break;
case Orientation.RightTop:
new RotateProcessor<TPixel>((int)RotateType.Rotate90).Apply(source, sourceRectangle);
new RotateProcessor<TPixel>((int)RotateType.Rotate90, size).Apply(source, sourceRectangle);
break;
case Orientation.RightBottom:
new FlipProcessor<TPixel>(FlipType.Vertical).Apply(source, sourceRectangle);
new RotateProcessor<TPixel>((int)RotateType.Rotate270).Apply(source, sourceRectangle);
new RotateProcessor<TPixel>((int)RotateType.Rotate270, size).Apply(source, sourceRectangle);
break;
case Orientation.LeftBottom:
new RotateProcessor<TPixel>((int)RotateType.Rotate270).Apply(source, sourceRectangle);
new RotateProcessor<TPixel>((int)RotateType.Rotate270, size).Apply(source, sourceRectangle);
break;
case Orientation.Unknown:

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

@ -19,8 +19,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
protected CenteredAffineTransformProcessor(Matrix3x2 matrix, IResampler sampler)
: base(matrix, sampler)
/// <param name="sourceSize">The source image size</param>
protected CenteredAffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size sourceSize)
: base(matrix, sampler, GetTransformedDimensions(sourceSize, matrix))
{
}
@ -30,11 +31,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
return TransformHelpers.GetCenteredTransformMatrix(sourceRectangle, destinationRectangle, this.TransformMatrix);
}
/// <inheritdoc/>
protected override Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix)
private static Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix)
{
var sourceRectangle = new Rectangle(0, 0, sourceDimensions.Width, sourceDimensions.Height);
return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, this.TransformMatrix).Size;
return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, matrix).Size;
}
}
}

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

@ -19,8 +19,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
protected CenteredProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler)
: base(matrix, sampler)
/// <param name="sourceSize">The source image size</param>
protected CenteredProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size sourceSize)
: base(matrix, sampler, GetTransformedDimensions(sourceSize, matrix))
{
}
@ -30,11 +31,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
return TransformHelpers.GetCenteredTransformMatrix(sourceRectangle, destinationRectangle, this.TransformMatrix);
}
/// <inheritdoc/>
protected override Size GetTransformedDimensions(Size sourceDimensions, Matrix4x4 matrix)
private static Size GetTransformedDimensions(Size sourceDimensions, Matrix4x4 matrix)
{
var sourceRectangle = new Rectangle(0, 0, sourceDimensions.Width, sourceDimensions.Height);
return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, this.TransformMatrix).Size;
return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, matrix).Size;
}
}
}

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

@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
// TODO: Convert this into a cloning processor inheriting TransformProcessor once Anton's memory PR is merged
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>

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

@ -22,27 +22,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
internal class ProjectiveTransformProcessor<TPixel> : InterpolatedTransformProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private Size targetDimensions;
/// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
public ProjectiveTransformProcessor(Matrix4x4 matrix)
: this(matrix, KnownResamplers.Bicubic)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler)
: this(matrix, sampler, Size.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class.
/// </summary>
@ -53,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
: base(sampler)
{
this.TransformMatrix = matrix;
this.targetDimensions = targetDimensions;
this.TargetDimensions = targetDimensions;
}
/// <summary>
@ -61,18 +40,17 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
public Matrix4x4 TransformMatrix { get; }
/// <summary>
/// Gets the target dimensions to constrain the transformed image to
/// </summary>
public Size TargetDimensions { get; }
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
if (this.targetDimensions == Size.Empty)
{
// TODO: CreateDestination() should not modify the processors state! (kinda CQRS)
this.targetDimensions = this.GetTransformedDimensions(sourceRectangle.Size, this.TransformMatrix);
}
// 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>(this.targetDimensions.Width, this.targetDimensions.Height, x.MetaData.Clone()));
source.Frames.Select(x => new ImageFrame<TPixel>(this.TargetDimensions.Width, this.TargetDimensions.Height, x.MetaData.Clone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.Clone(), frames);
@ -81,8 +59,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration)
{
int height = this.targetDimensions.Height;
int width = this.targetDimensions.Width;
int height = this.TargetDimensions.Height;
int width = this.TargetDimensions.Width;
Rectangle sourceBounds = source.Bounds();
var targetBounds = new Rectangle(0, 0, width, height);
@ -202,8 +180,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
var vector = source[i, j].ToVector4();
// Values are first premultiplied to prevent darkening of edge pixels
Vector4 mupltiplied = vector.Premultiply();
sum += mupltiplied * xWeight * yWeight;
Vector4 multiplied = vector.Premultiply();
sum += multiplied * xWeight * yWeight;
}
}
@ -228,16 +206,5 @@ namespace SixLabors.ImageSharp.Processing.Processors
{
return this.TransformMatrix;
}
/// <summary>
/// Gets the bounding <see cref="Rectangle"/> relative to the source for the given transformation matrix.
/// </summary>
/// <param name="sourceDimensions">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Rectangle"/></returns>
protected virtual Size GetTransformedDimensions(Size sourceDimensions, Matrix4x4 matrix)
{
return sourceDimensions;
}
}
}

164
src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs

@ -1,164 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// Provides methods that allow the resizing of images using various algorithms.
/// Adapted from <see href="http://www.realtimerendering.com/resources/GraphicsGems/gemsiii/filter_rcg.c"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class ResamplingWeightedProcessor<TPixel> : TransformProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="ResamplingWeightedProcessor{TPixel}"/> class.
/// </summary>
/// <param name="sampler">The sampler to perform the resize operation.</param>
/// <param name="width">The target width.</param>
/// <param name="height">The target height.</param>
/// <param name="resizeRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the target image object to draw to.
/// </param>
protected ResamplingWeightedProcessor(IResampler sampler, int width, int height, Rectangle resizeRectangle)
{
Guard.NotNull(sampler, nameof(sampler));
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.Sampler = sampler;
this.Width = width;
this.Height = height;
this.ResizeRectangle = resizeRectangle;
}
/// <summary>
/// Gets the sampler to perform the resize operation.
/// </summary>
public IResampler Sampler { get; }
/// <summary>
/// Gets or sets the width.
/// </summary>
public int Width { get; protected set; }
/// <summary>
/// Gets or sets the height.
/// </summary>
public int Height { get; protected set; }
/// <summary>
/// Gets or sets the resize rectangle.
/// </summary>
public Rectangle ResizeRectangle { get; protected set; }
/// <summary>
/// Gets or sets the horizontal weights.
/// </summary>
protected WeightsBuffer HorizontalWeights { get; set; }
/// <summary>
/// Gets or sets the vertical weights.
/// </summary>
protected WeightsBuffer VerticalWeights { get; set; }
/// <summary>
/// Computes the weights to apply at each pixel when resizing.
/// </summary>
/// <param name="destinationSize">The destination size</param>
/// <param name="sourceSize">The source size</param>
/// <returns>The <see cref="WeightsBuffer"/></returns>
// TODO: Made internal to simplify experimenting with weights data. Make it protected again when finished figuring out how to optimize all the stuff!
internal unsafe WeightsBuffer PrecomputeWeights(int destinationSize, int sourceSize)
{
float ratio = (float)sourceSize / destinationSize;
float scale = ratio;
if (scale < 1F)
{
scale = 1F;
}
IResampler sampler = this.Sampler;
float radius = MathF.Ceiling(scale * sampler.Radius);
var result = new WeightsBuffer(sourceSize, destinationSize);
for (int i = 0; i < destinationSize; i++)
{
float center = ((i + .5F) * ratio) - .5F;
// Keep inside bounds.
int left = (int)Math.Ceiling(center - radius);
if (left < 0)
{
left = 0;
}
int right = (int)Math.Floor(center + radius);
if (right > sourceSize - 1)
{
right = sourceSize - 1;
}
float sum = 0;
WeightsWindow ws = result.GetWeightsWindow(i, left, right);
result.Weights[i] = ws;
ref float weightsBaseRef = ref ws.GetStartReference();
for (int j = left; j <= right; j++)
{
float weight = sampler.GetValue((j - center) / scale);
sum += weight;
// weights[j - left] = weight:
Unsafe.Add(ref weightsBaseRef, j - left) = weight;
}
// Normalise, best to do it here rather than in the pixel loop later on.
if (sum > 0)
{
for (int w = 0; w < ws.Length; w++)
{
// weights[w] = weights[w] / sum:
ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w);
wRef = wRef / sum;
}
}
}
return result;
}
/// <inheritdoc/>
protected override void BeforeApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration)
{
if (!(this.Sampler is NearestNeighborResampler))
{
this.HorizontalWeights = this.PrecomputeWeights(
this.ResizeRectangle.Width,
sourceRectangle.Width);
this.VerticalWeights = this.PrecomputeWeights(
this.ResizeRectangle.Height,
sourceRectangle.Height);
}
}
/// <inheritdoc />
protected override void AfterApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration)
{
base.AfterApply(source, destination, sourceRectangle, configuration);
this.HorizontalWeights?.Dispose();
this.HorizontalWeights = null;
this.VerticalWeights?.Dispose();
this.VerticalWeights = null;
}
}
}

205
src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

@ -5,10 +5,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@ -16,19 +16,62 @@ namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// Provides methods that allow the resizing of images using various algorithms.
/// Adapted from <see href="http://www.realtimerendering.com/resources/GraphicsGems/gemsiii/filter_rcg.c"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class ResizeProcessor<TPixel> : ResamplingWeightedProcessor<TPixel>
internal class ResizeProcessor<TPixel> : TransformProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
// The following fields are not immutable but are optionally created on demand.
private WeightsBuffer horizontalWeights;
private WeightsBuffer verticalWeights;
/// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor{TPixel}"/> class.
/// </summary>
/// <param name="options">The resize options</param>
/// <param name="sourceSize">The source image size</param>
public ResizeProcessor(ResizeOptions options, Size sourceSize)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(options.Sampler, nameof(options.Sampler));
int tempWidth = options.Size.Width;
int tempHeight = options.Size.Height;
// Ensure size is populated across both dimensions.
// These dimensions are used to calculate the final dimensions determined by the mode algorithm.
if (tempWidth == 0 && tempHeight > 0)
{
tempWidth = (int)MathF.Round(sourceSize.Width * tempHeight / (float)sourceSize.Height);
}
if (tempHeight == 0 && tempWidth > 0)
{
tempHeight = (int)MathF.Round(sourceSize.Height * tempWidth / (float)sourceSize.Width);
}
Guard.MustBeGreaterThan(tempWidth, 0, nameof(tempWidth));
Guard.MustBeGreaterThan(tempHeight, 0, nameof(tempHeight));
(Size size, Rectangle rectangle) locationBounds = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options, tempWidth, tempHeight);
this.Sampler = options.Sampler;
this.Width = locationBounds.size.Width;
this.Height = locationBounds.size.Height;
this.ResizeRectangle = locationBounds.rectangle;
this.Compand = options.Compand;
}
/// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor{TPixel}"/> class.
/// </summary>
/// <param name="sampler">The sampler to perform the resize operation.</param>
/// <param name="width">The target width.</param>
/// <param name="height">The target height.</param>
public ResizeProcessor(IResampler sampler, int width, int height)
: base(sampler, width, height, new Rectangle(0, 0, width, height))
/// <param name="sourceSize">The source image size</param>
public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize)
: this(sampler, width, height, sourceSize, new Rectangle(0, 0, width, height), false)
{
}
@ -38,18 +81,131 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="sampler">The sampler to perform the resize operation.</param>
/// <param name="width">The target width.</param>
/// <param name="height">The target height.</param>
/// <param name="sourceSize">The source image size</param>
/// <param name="resizeRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the target image object to draw to.
/// </param>
public ResizeProcessor(IResampler sampler, int width, int height, Rectangle resizeRectangle)
: base(sampler, width, height, resizeRectangle)
/// <param name="compand">Whether to compress or expand individual pixel color values on processing.</param>
public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize, Rectangle resizeRectangle, bool compand)
{
Guard.NotNull(sampler, nameof(sampler));
// Ensure size is populated across both dimensions.
if (width == 0 && height > 0)
{
width = (int)MathF.Round(sourceSize.Width * height / (float)sourceSize.Height);
resizeRectangle.Width = width;
}
if (height == 0 && width > 0)
{
height = (int)MathF.Round(sourceSize.Height * width / (float)sourceSize.Width);
resizeRectangle.Height = height;
}
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.Sampler = sampler;
this.Width = width;
this.Height = height;
this.ResizeRectangle = resizeRectangle;
this.Compand = compand;
}
/// <summary>
/// Gets or sets a value indicating whether to compress or expand individual pixel color values on processing.
/// Gets the sampler to perform the resize operation.
/// </summary>
public bool Compand { get; set; }
public IResampler Sampler { get; }
/// <summary>
/// Gets the target width.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the target height.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the resize rectangle.
/// </summary>
public Rectangle ResizeRectangle { get; }
/// <summary>
/// Gets a value indicating whether to compress or expand individual pixel color values on processing.
/// </summary>
public bool Compand { get; }
/// <summary>
/// Computes the weights to apply at each pixel when resizing.
/// </summary>
/// <param name="destinationSize">The destination size</param>
/// <param name="sourceSize">The source size</param>
/// <returns>The <see cref="WeightsBuffer"/></returns>
// TODO: Made internal to simplify experimenting with weights data. Make it private when finished figuring out how to optimize all the stuff!
internal WeightsBuffer PrecomputeWeights(int destinationSize, int sourceSize)
{
float ratio = (float)sourceSize / destinationSize;
float scale = ratio;
if (scale < 1F)
{
scale = 1F;
}
IResampler sampler = this.Sampler;
float radius = MathF.Ceiling(scale * sampler.Radius);
var result = new WeightsBuffer(sourceSize, destinationSize);
for (int i = 0; i < destinationSize; i++)
{
float center = ((i + .5F) * ratio) - .5F;
// Keep inside bounds.
int left = (int)Math.Ceiling(center - radius);
if (left < 0)
{
left = 0;
}
int right = (int)Math.Floor(center + radius);
if (right > sourceSize - 1)
{
right = sourceSize - 1;
}
float sum = 0;
WeightsWindow ws = result.GetWeightsWindow(i, left, right);
result.Weights[i] = ws;
ref float weightsBaseRef = ref ws.GetStartReference();
for (int j = left; j <= right; j++)
{
float weight = sampler.GetValue((j - center) / scale);
sum += weight;
// weights[j - left] = weight:
Unsafe.Add(ref weightsBaseRef, j - left) = weight;
}
// Normalize, best to do it here rather than in the pixel loop later on.
if (sum > 0)
{
for (int w = 0; w < ws.Length; w++)
{
// weights[w] = weights[w] / sum:
ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w);
wRef = wRef / sum;
}
}
}
return result;
}
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
@ -62,6 +218,21 @@ namespace SixLabors.ImageSharp.Processing.Processors
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.Clone(), frames);
}
/// <inheritdoc/>
protected override void BeforeApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration)
{
if (!(this.Sampler is NearestNeighborResampler))
{
this.horizontalWeights = this.PrecomputeWeights(
this.ResizeRectangle.Width,
sourceRectangle.Width);
this.verticalWeights = this.PrecomputeWeights(
this.ResizeRectangle.Height,
sourceRectangle.Height);
}
}
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, ImageFrame<TPixel> cloned, Rectangle sourceRectangle, Configuration configuration)
{
@ -139,7 +310,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
{
for (int x = minX; x < maxX; x++)
{
WeightsWindow window = this.HorizontalWeights.Weights[x - startX];
WeightsWindow window = this.horizontalWeights.Weights[x - startX];
firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX);
}
}
@ -147,7 +318,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
{
for (int x = minX; x < maxX; x++)
{
WeightsWindow window = this.HorizontalWeights.Weights[x - startX];
WeightsWindow window = this.horizontalWeights.Weights[x - startX];
firstPassRow[x] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX);
}
}
@ -161,8 +332,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
configuration.ParallelOptions,
y =>
{
// Ensure offsets are normalised for cropping and padding.
WeightsWindow window = this.VerticalWeights.Weights[y - startY];
// Ensure offsets are normalized for cropping and padding.
WeightsWindow window = this.verticalWeights.Weights[y - startY];
Span<TPixel> targetRow = cloned.GetPixelRowSpan(y);
if (this.Compand)
@ -191,5 +362,15 @@ namespace SixLabors.ImageSharp.Processing.Processors
});
}
}
/// <inheritdoc />
protected override void AfterApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration)
{
base.AfterApply(source, destination, sourceRectangle, configuration);
this.horizontalWeights?.Dispose();
this.horizontalWeights = null;
this.verticalWeights?.Dispose();
this.verticalWeights = null;
}
}
}

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

@ -22,8 +22,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class.
/// </summary>
/// <param name="degrees">The angle of rotation in degrees.</param>
public RotateProcessor(float degrees)
: this(degrees, KnownResamplers.Bicubic)
/// <param name="sourceSize">The source image size</param>
public RotateProcessor(float degrees, Size sourceSize)
: this(degrees, KnownResamplers.Bicubic, sourceSize)
{
}
@ -32,8 +33,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
/// <param name="degrees">The angle of rotation in degrees.</param>
/// <param name="sampler">The sampler to perform the rotating operation.</param>
public RotateProcessor(float degrees, IResampler sampler)
: base(Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty), sampler)
/// <param name="sourceSize">The source image size</param>
public RotateProcessor(float degrees, IResampler sampler, Size sourceSize)
: base(Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty), sampler, sourceSize)
{
this.Degrees = degrees;
}

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

@ -18,8 +18,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
/// <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>
public SkewProcessor(float degreesX, float degreesY)
: this(degreesX, degreesY, KnownResamplers.Bicubic)
/// <param name="sourceSize">The source image size</param>
public SkewProcessor(float degreesX, float degreesY, Size sourceSize)
: this(degreesX, degreesY, KnownResamplers.Bicubic, sourceSize)
{
}
@ -29,8 +30,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <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="sampler">The sampler to perform the skew operation.</param>
public SkewProcessor(float degreesX, float degreesY, IResampler sampler)
: base(Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty), sampler)
/// <param name="sourceSize">The source image size</param>
public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize)
: base(Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty), sampler, sourceSize)
{
this.DegreesX = degreesX;
this.DegreesY = degreesY;

119
src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs

@ -3,7 +3,6 @@
using System;
using System.Linq;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
@ -17,52 +16,39 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Calculates the target location and bounds to perform the resize operation against.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image.</param>
/// <param name="sourceSize">The source image size.</param>
/// <param name="options">The resize options.</param>
/// <param name="width">The target width</param>
/// <param name="height">The target height</param>
/// <returns>
/// The <see cref="Rectangle"/>.
/// The <see cref="ValueTuple{Size,Rectangle}"/>.
/// </returns>
public static Rectangle CalculateTargetLocationAndBounds<TPixel>(ImageFrame<TPixel> source, ResizeOptions options)
where TPixel : struct, IPixel<TPixel>
public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options, int width, int height)
{
switch (options.Mode)
{
case ResizeMode.Crop:
return CalculateCropRectangle(source, options);
return CalculateCropRectangle(sourceSize, options, width, height);
case ResizeMode.Pad:
return CalculatePadRectangle(source, options);
return CalculatePadRectangle(sourceSize, options, width, height);
case ResizeMode.BoxPad:
return CalculateBoxPadRectangle(source, options);
return CalculateBoxPadRectangle(sourceSize, options, width, height);
case ResizeMode.Max:
return CalculateMaxRectangle(source, options);
return CalculateMaxRectangle(sourceSize, options, width, height);
case ResizeMode.Min:
return CalculateMinRectangle(source, options);
return CalculateMinRectangle(sourceSize, options, width, height);
// Last case ResizeMode.Stretch:
default:
return new Rectangle(0, 0, options.Size.Width, options.Size.Height);
return (new Size(width, height), new Rectangle(0, 0, width, height));
}
}
/// <summary>
/// Calculates the target rectangle for crop mode.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image.</param>
/// <param name="options">The resize options.</param>
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
private static Rectangle CalculateCropRectangle<TPixel>(ImageFrame<TPixel> source, ResizeOptions options)
where TPixel : struct, IPixel<TPixel>
private static (Size, Rectangle) CalculateCropRectangle(Size source, ResizeOptions options, int width, int height)
{
int width = options.Size.Width;
int height = options.Size.Height;
if (width <= 0 || height <= 0)
{
return new Rectangle(0, 0, source.Width, source.Height);
return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
}
float ratio;
@ -161,27 +147,14 @@ namespace SixLabors.ImageSharp.Processing
destinationWidth = (int)MathF.Ceiling(sourceWidth * percentHeight);
}
return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight);
return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
}
/// <summary>
/// Calculates the target rectangle for pad mode.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image.</param>
/// <param name="options">The resize options.</param>
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
private static Rectangle CalculatePadRectangle<TPixel>(ImageFrame<TPixel> source, ResizeOptions options)
where TPixel : struct, IPixel<TPixel>
private static (Size, Rectangle) CalculatePadRectangle(Size source, ResizeOptions options, int width, int height)
{
int width = options.Size.Width;
int height = options.Size.Height;
if (width <= 0 || height <= 0)
{
return new Rectangle(0, 0, source.Width, source.Height);
return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
}
float ratio;
@ -242,27 +215,14 @@ namespace SixLabors.ImageSharp.Processing
}
}
return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight);
return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
}
/// <summary>
/// Calculates the target rectangle for box pad mode.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image.</param>
/// <param name="options">The resize options.</param>
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
private static Rectangle CalculateBoxPadRectangle<TPixel>(ImageFrame<TPixel> source, ResizeOptions options)
where TPixel : struct, IPixel<TPixel>
private static (Size, Rectangle) CalculateBoxPadRectangle(Size source, ResizeOptions options, int width, int height)
{
int width = options.Size.Width;
int height = options.Size.Height;
if (width <= 0 || height <= 0)
{
return new Rectangle(0, 0, source.Width, source.Height);
return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
}
int sourceWidth = source.Width;
@ -325,27 +285,15 @@ namespace SixLabors.ImageSharp.Processing
break;
}
return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight);
return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
}
// Switch to pad mode to downscale and calculate from there.
return CalculatePadRectangle(source, options);
return CalculatePadRectangle(source, options, width, height);
}
/// <summary>
/// Calculates the target rectangle for max mode.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image.</param>
/// <param name="options">The resize options.</param>
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
private static Rectangle CalculateMaxRectangle<TPixel>(ImageFrame<TPixel> source, ResizeOptions options)
where TPixel : struct, IPixel<TPixel>
private static (Size, Rectangle) CalculateMaxRectangle(Size source, ResizeOptions options, int width, int height)
{
int width = options.Size.Width;
int height = options.Size.Height;
int destinationWidth = width;
int destinationHeight = height;
@ -369,24 +317,11 @@ namespace SixLabors.ImageSharp.Processing
}
// Replace the size to match the rectangle.
options.Size = new Size(width, height);
return new Rectangle(0, 0, destinationWidth, destinationHeight);
return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight));
}
/// <summary>
/// Calculates the target rectangle for min mode.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image.</param>
/// <param name="options">The resize options.</param>
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
private static Rectangle CalculateMinRectangle<TPixel>(ImageFrame<TPixel> source, ResizeOptions options)
where TPixel : struct, IPixel<TPixel>
private static (Size, Rectangle) CalculateMinRectangle(Size source, ResizeOptions options, int width, int height)
{
int width = options.Size.Width;
int height = options.Size.Height;
int sourceWidth = source.Width;
int sourceHeight = source.Height;
int destinationWidth;
@ -395,8 +330,7 @@ namespace SixLabors.ImageSharp.Processing
// Don't upscale
if (width > sourceWidth || height > sourceHeight)
{
options.Size = new Size(sourceWidth, sourceHeight);
return new Rectangle(0, 0, sourceWidth, sourceHeight);
return (new Size(sourceWidth, sourceWidth), new Rectangle(0, 0, sourceWidth, sourceHeight));
}
// Fractional variants for preserving aspect ratio.
@ -438,8 +372,7 @@ namespace SixLabors.ImageSharp.Processing
}
// Replace the size to match the rectangle.
options.Size = new Size(width, height);
return new Rectangle(0, 0, destinationWidth, destinationHeight);
return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight));
}
}
}

2
src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs

@ -41,6 +41,6 @@ namespace SixLabors.ImageSharp.Processing
/// Gets or sets a value indicating whether to compress
/// or expand individual pixel colors the value on processing.
/// </summary>
public bool Compand { get; set; }
public bool Compand { get; set; } = false;
}
}

120
src/ImageSharp/Processing/Transforms/Resize.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors;
@ -24,29 +23,10 @@ namespace SixLabors.ImageSharp
/// <remarks>Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image</remarks>
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, ResizeOptions options)
where TPixel : struct, IPixel<TPixel>
{
return source.Apply(img =>
{
// Cheat and bound through a run, inside here we should just be mutating, this really needs moving over to a processor
// Ensure size is populated across both dimensions.
if (options.Size.Width == 0 && options.Size.Height > 0)
{
options.Size = new Size((int)MathF.Round(img.Width * options.Size.Height / (float)img.Height), options.Size.Height);
}
if (options.Size.Height == 0 && options.Size.Width > 0)
{
options.Size = new Size(options.Size.Width, (int)MathF.Round(img.Height * options.Size.Width / (float)img.Width));
}
Rectangle targetRectangle = ResizeHelper.CalculateTargetLocationAndBounds(img.Frames.RootFrame, options);
img.Mutate(x => Resize(x, options.Size.Width, options.Size.Height, options.Sampler, targetRectangle, options.Compand));
});
}
=> source.ApplyProcessor(new ResizeProcessor<TPixel>(options, source.Bounds().Size));
/// <summary>
/// Resizes an image to the given <see cref="SixLabors.Primitives.Size"/>.
/// Resizes an image to the given <see cref="Size"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to resize.</param>
@ -55,12 +35,10 @@ namespace SixLabors.ImageSharp
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, Size size)
where TPixel : struct, IPixel<TPixel>
{
return Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false);
}
=> Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false);
/// <summary>
/// Resizes an image to the given <see cref="SixLabors.Primitives.Size"/>.
/// Resizes an image to the given <see cref="Size"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to resize.</param>
@ -70,9 +48,7 @@ namespace SixLabors.ImageSharp
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, Size size, bool compand)
where TPixel : struct, IPixel<TPixel>
{
return Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand);
}
=> Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand);
/// <summary>
/// Resizes an image to the given width and height.
@ -85,9 +61,7 @@ namespace SixLabors.ImageSharp
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
return Resize(source, width, height, KnownResamplers.Bicubic, false);
}
=> Resize(source, width, height, KnownResamplers.Bicubic, false);
/// <summary>
/// Resizes an image to the given width and height.
@ -101,9 +75,7 @@ namespace SixLabors.ImageSharp
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height, bool compand)
where TPixel : struct, IPixel<TPixel>
{
return Resize(source, width, height, KnownResamplers.Bicubic, compand);
}
=> Resize(source, width, height, KnownResamplers.Bicubic, compand);
/// <summary>
/// Resizes an image to the given width and height with the given sampler.
@ -117,9 +89,7 @@ namespace SixLabors.ImageSharp
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height, IResampler sampler)
where TPixel : struct, IPixel<TPixel>
{
return Resize(source, width, height, sampler, false);
}
=> Resize(source, width, height, sampler, false);
/// <summary>
/// Resizes an image to the given width and height with the given sampler.
@ -133,9 +103,7 @@ namespace SixLabors.ImageSharp
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, Size size, IResampler sampler, bool compand)
where TPixel : struct, IPixel<TPixel>
{
return Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand);
}
=> Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand);
/// <summary>
/// Resizes an image to the given width and height with the given sampler.
@ -150,9 +118,7 @@ namespace SixLabors.ImageSharp
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height, IResampler sampler, bool compand)
where TPixel : struct, IPixel<TPixel>
{
return Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand);
}
=> Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand);
/// <summary>
/// Resizes an image to the given width and height with the given sampler and
@ -172,34 +138,19 @@ namespace SixLabors.ImageSharp
/// <param name="compand">Whether to compress and expand the image color-space to gamma correct the image during processing.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand)
public static IImageProcessingContext<TPixel> Resize<TPixel>(
this IImageProcessingContext<TPixel> source,
int width,
int height,
IResampler sampler,
Rectangle sourceRectangle,
Rectangle targetRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel>
{
return source.Apply(img =>
{
// TODO : Stop cheating here and move this stuff into the processors itself
if (width == 0 && height > 0)
{
width = (int)MathF.Round(img.Width * height / (float)img.Height);
targetRectangle.Width = width;
}
if (height == 0 && width > 0)
{
height = (int)MathF.Round(img.Height * width / (float)img.Width);
targetRectangle.Height = height;
}
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
img.Mutate(x => x.ApplyProcessor(new ResizeProcessor<TPixel>(sampler, width, height, targetRectangle) { Compand = compand }, sourceRectangle));
});
}
=> source.ApplyProcessor(new ResizeProcessor<TPixel>(sampler, width, height, source.Bounds().Size, targetRectangle, compand), sourceRectangle);
/// <summary>
/// Resizes an image to the given width and height with the given sampler and
/// source rectangle.
/// Resizes an image to the given width and height with the given sampler and source rectangle.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to resize.</param>
@ -212,29 +163,14 @@ namespace SixLabors.ImageSharp
/// <param name="compand">Whether to compress and expand the image color-space to gamma correct the image during processing.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height, IResampler sampler, Rectangle targetRectangle, bool compand)
public static IImageProcessingContext<TPixel> Resize<TPixel>(
this IImageProcessingContext<TPixel> source,
int width,
int height,
IResampler sampler,
Rectangle targetRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel>
{
return source.Apply(img =>
{
// TODO : stop cheating here and move this stuff into the processors itself
if (width == 0 && height > 0)
{
width = (int)MathF.Round(img.Width * height / (float)img.Height);
targetRectangle.Width = width;
}
if (height == 0 && width > 0)
{
height = (int)MathF.Round(img.Height * width / (float)img.Width);
targetRectangle.Height = height;
}
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
img.Mutate(x => x.ApplyProcessor(new ResizeProcessor<TPixel>(sampler, width, height, targetRectangle) { Compand = compand }));
});
}
=> source.ApplyProcessor(new ResizeProcessor<TPixel>(sampler, width, height, source.Bounds().Size, targetRectangle, compand));
}
}

2
src/ImageSharp/Processing/Transforms/Rotate.cs

@ -44,6 +44,6 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, float degrees, IResampler sampler)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new RotateProcessor<TPixel>(degrees, sampler));
=> source.ApplyProcessor(new RotateProcessor<TPixel>(degrees, sampler, source.Bounds().Size));
}
}

2
src/ImageSharp/Processing/Transforms/Skew.cs

@ -35,6 +35,6 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Skew<TPixel>(this IImageProcessingContext<TPixel> source, float degreesX, float degreesY, IResampler sampler)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new SkewProcessor<TPixel>(degreesX, degreesY, sampler));
=> source.ApplyProcessor(new SkewProcessor<TPixel>(degreesX, degreesY, sampler, source.Bounds().Size));
}
}

4
src/ImageSharp/Processing/Transforms/Transform.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix3x2 matrix, IResampler sampler)
where TPixel : struct, IPixel<TPixel>
=> Transform(source, matrix, sampler, Size.Empty);
=> Transform(source, matrix, sampler, source.Bounds());
/// <summary>
/// Transforms an image by the given matrix using the specified sampling algorithm
@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns>
internal static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix, IResampler sampler)
where TPixel : struct, IPixel<TPixel>
=> Transform(source, matrix, sampler, Rectangle.Empty);
=> Transform(source, matrix, sampler, source.Bounds());
/// <summary>
/// Applies a projective transform to the image by the given matrix using the specified sampling algorithm.

14
tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs

@ -16,29 +16,31 @@ namespace SixLabors.ImageSharp.Tests
private readonly FakeImageOperationsProvider.FakeImageOperations<Rgba32> internalOperations;
protected readonly Rectangle rect;
protected readonly GraphicsOptions options;
private Image<Rgba32> source;
public BaseImageOperationsExtensionTest()
{
this.options = new GraphicsOptions(false) { };
this.options = new GraphicsOptions(false);
this.source = new Image<Rgba32>(91 + 324, 123 + 56);
this.rect = new Rectangle(91, 123, 324, 56); // make this random?
this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(null, false);
this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(this.source, false);
this.operations = this.internalOperations;
}
public T Verify<T>(int index = 0)
{
Assert.InRange(index, 0, this.internalOperations.applied.Count - 1);
Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1);
var operation = this.internalOperations.applied[index];
var operation = this.internalOperations.Applied[index];
return Assert.IsType<T>(operation.Processor);
}
public T Verify<T>(Rectangle rect, int index = 0)
{
Assert.InRange(index, 0, this.internalOperations.applied.Count - 1);
Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1);
var operation = this.internalOperations.applied[index];
var operation = this.internalOperations.Applied[index];
Assert.Equal(rect, operation.Rectangle);
return Assert.IsType<T>(operation.Processor);

42
tests/ImageSharp.Tests/FakeImageOperationsProvider.cs

@ -1,12 +1,11 @@
// 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.Text;
using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Tests
@ -23,13 +22,13 @@ namespace SixLabors.ImageSharp.Tests
public IEnumerable<FakeImageOperations<TPixel>> Created<TPixel>(Image<TPixel> source) where TPixel : struct, IPixel<TPixel>
{
return this.ImageOperators.OfType<FakeImageOperations<TPixel>>()
.Where(x => x.source == source);
.Where(x => x.Source == source);
}
public IEnumerable<FakeImageOperations<TPixel>.AppliedOpperation> AppliedOperations<TPixel>(Image<TPixel> source) where TPixel : struct, IPixel<TPixel>
public IEnumerable<FakeImageOperations<TPixel>.AppliedOperation> AppliedOperations<TPixel>(Image<TPixel> source) where TPixel : struct, IPixel<TPixel>
{
return Created(source)
.SelectMany(x => x.applied);
.SelectMany(x => x.Applied);
}
public IInternalImageProcessingContext<TPixel> CreateImageProcessingContext<TPixel>(Image<TPixel> source, bool mutate) where TPixel : struct, IPixel<TPixel>
@ -43,32 +42,31 @@ namespace SixLabors.ImageSharp.Tests
public class FakeImageOperations<TPixel> : IInternalImageProcessingContext<TPixel>
where TPixel : struct, IPixel<TPixel>
{
public Image<TPixel> source;
public List<AppliedOpperation> applied = new List<AppliedOpperation>();
public bool mutate;
private bool mutate;
public FakeImageOperations(Image<TPixel> source, bool mutate)
{
this.mutate = mutate;
if (mutate)
{
this.source = source;
}
else
{
this.source = source?.Clone();
}
this.Source = mutate ? source : source?.Clone();
}
public Image<TPixel> Source { get; }
public List<AppliedOperation> Applied { get; } = new List<AppliedOperation>();
public Image<TPixel> Apply()
{
return source;
return this.Source;
}
public Rectangle Bounds()
{
return this.Source.Bounds();
}
public IImageProcessingContext<TPixel> ApplyProcessor(IImageProcessor<TPixel> processor, Rectangle rectangle)
{
applied.Add(new AppliedOpperation
this.Applied.Add(new AppliedOperation
{
Processor = processor,
Rectangle = rectangle
@ -78,13 +76,13 @@ namespace SixLabors.ImageSharp.Tests
public IImageProcessingContext<TPixel> ApplyProcessor(IImageProcessor<TPixel> processor)
{
applied.Add(new AppliedOpperation
this.Applied.Add(new AppliedOperation
{
Processor = processor
});
return this;
}
public struct AppliedOpperation
public struct AppliedOperation
{
public Rectangle? Rectangle { get; set; }
public IImageProcessor<TPixel> Processor { get; set; }

2
tests/ImageSharp.Tests/ImageOperationTests.cs

@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests
{
var operations = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(null, false);
operations.ApplyProcessors(this.processor);
Assert.Contains(this.processor, operations.applied.Select(x => x.Processor));
Assert.Contains(this.processor, operations.Applied.Select(x => x.Processor));
}
public void Dispose()

7
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs

@ -11,6 +11,8 @@ using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
using SixLabors.Primitives;
public class ResizeProfilingBenchmarks : MeasureFixture
{
public ResizeProfilingBenchmarks(ITestOutputHelper output)
@ -38,9 +40,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
// [Fact]
public void PrintWeightsData()
{
var proc = new ResizeProcessor<Rgba32>(KnownResamplers.Bicubic, 200, 200);
var size = new Size(500, 500);
var proc = new ResizeProcessor<Rgba32>(KnownResamplers.Bicubic, 200, 200, size);
WeightsBuffer weights = proc.PrecomputeWeights(200, 500);
WeightsBuffer weights = proc.PrecomputeWeights(proc.Width, size.Width);
var bld = new StringBuilder();

Loading…
Cancel
Save