Browse Source

Merge pull request #474 from SixLabors/js/fix-resize-transform-immutability

Make resize and transform processors immutable #473
pull/483/head
James Jackson-South 8 years ago
committed by GitHub
parent
commit
88bbc3f789
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      src/ImageSharp/DefaultInternalImageProcessorContext.cs
  2. 8
      src/ImageSharp/IImageProcessingContext{TPixel}.cs
  3. 3
      src/ImageSharp/Processing/Processors/CloningImageProcessor.cs
  4. 55
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  5. 12
      src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs
  6. 10
      src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs
  7. 10
      src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs
  8. 1
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
  9. 60
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  10. 171
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
  11. 226
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  12. 10
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  13. 10
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  14. 119
      src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs
  15. 2
      src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs
  16. 6
      src/ImageSharp/Processing/Transforms/Pad.cs
  17. 120
      src/ImageSharp/Processing/Transforms/Resize.cs
  18. 2
      src/ImageSharp/Processing/Transforms/Rotate.cs
  19. 2
      src/ImageSharp/Processing/Transforms/Skew.cs
  20. 4
      src/ImageSharp/Processing/Transforms/Transform.cs
  21. 14
      tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs
  22. 47
      tests/ImageSharp.Tests/FakeImageOperationsProvider.cs
  23. 58
      tests/ImageSharp.Tests/Image/ImageProcessingContextTests.cs
  24. 2
      tests/ImageSharp.Tests/ImageOperationTests.cs
  25. 10
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
  26. 24
      tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs
  27. 84
      tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs

10
src/ImageSharp/DefaultInternalImageProcessorContext.cs

@ -51,6 +51,9 @@ namespace SixLabors.ImageSharp
return this.destination;
}
/// <inheritdoc/>
public Size GetCurrentSize() => this.GetCurrentBounds().Size;
/// <inheritdoc/>
public IImageProcessingContext<TPixel> ApplyProcessor(IImageProcessor<TPixel> processor, Rectangle rectangle)
{
@ -75,7 +78,12 @@ namespace SixLabors.ImageSharp
/// <inheritdoc/>
public IImageProcessingContext<TPixel> ApplyProcessor(IImageProcessor<TPixel> processor)
{
return this.ApplyProcessor(processor, this.source.Bounds());
return this.ApplyProcessor(processor, this.GetCurrentBounds());
}
private Rectangle GetCurrentBounds()
{
return this.destination?.Bounds() ?? this.source.Bounds();
}
}
}

8
src/ImageSharp/IImageProcessingContext{TPixel}.cs

@ -21,6 +21,12 @@ namespace SixLabors.ImageSharp
/// </summary>
MemoryManager MemoryManager { get; }
/// <summary>
/// Gets the image dimensions at the current point in the processing pipeline.
/// </summary>
/// <returns>The <see cref="Rectangle"/></returns>
Size GetCurrentSize();
/// <summary>
/// Adds the processor to the current set of image operations to be applied.
/// </summary>
@ -47,7 +53,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();
}
}

3
src/ImageSharp/Processing/Processors/CloningImageProcessor.cs

@ -96,6 +96,7 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// This method is called before the process is applied to prepare the processor.
/// TODO: We should probably name this 'BeforeFrameApply'
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
@ -108,6 +109,7 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}" /> at the specified location
/// and with the specified size.
/// TODO: We should probably name this 'ApplyToFrame'
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
@ -117,6 +119,7 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// This method is called after the process is applied to prepare the processor.
/// TODO: We should probably name this 'AfterFrameApply'
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>

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>(source.GetMemoryManager(), this.targetDimensions, x.MetaData.Clone()));
source.Frames.Select(x => new ImageFrame<TPixel>(source.GetMemoryManager(), 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);
@ -207,8 +185,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;
}
}
@ -233,16 +211,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>

60
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,21 +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>(
source.GetMemoryManager(),
this.targetDimensions,
x.MetaData.Clone()));
IEnumerable<ImageFrame<TPixel>> frames =
source.Frames.Select(x => new ImageFrame<TPixel>(source.GetMemoryManager(), 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);
@ -84,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);
@ -207,8 +182,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;
}
}
@ -233,16 +208,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;
}
}
}

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

@ -1,171 +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.Memory;
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="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <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(MemoryManager memoryManager, IResampler sampler, int width, int height, Rectangle resizeRectangle)
{
Guard.NotNull(memoryManager, nameof(memoryManager));
Guard.NotNull(sampler, nameof(sampler));
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.MemoryManager = memoryManager;
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; }
protected MemoryManager MemoryManager { get; }
/// <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(this.MemoryManager, 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;
}
}
}

226
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,56 +16,224 @@ 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="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <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(MemoryManager memoryManager, IResampler sampler, int width, int height)
: base(memoryManager, 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)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor{TPixel}"/> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <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(MemoryManager memoryManager, IResampler sampler, int width, int height, Rectangle resizeRectangle)
: base(memoryManager, 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="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations</param>
/// <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(MemoryManager memoryManager, 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(memoryManager, 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)
{
Configuration config = source.GetConfiguration();
// 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.GetMemoryManager(), this.Width, this.Height, x.MetaData.Clone())); // this will create places holders
// We will always be creating the clone even for mutate because thats the way this base processor works
// ------------
// For resize we know we are going to populate every pixel with fresh data and we want a different target size so
// let's manually clone an empty set of images at the correct target and then have the base class process them in turn.
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration().MemoryManager, this.Width, this.Height, x.MetaData.Clone())); // this will create places holders
var image = new Image<TPixel>(config, source.MetaData.Clone(), frames); // base the place holder images in to prevent a extra frame being added
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.Clone(), frames);
}
return image;
/// <inheritdoc/>
protected override void BeforeApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration)
{
if (!(this.Sampler is NearestNeighborResampler))
{
// TODO: Optimization opportunity: if we could assume that all frames are of the same size, we can move this into 'BeforeImageApply()`
this.horizontalWeights = this.PrecomputeWeights(
source.MemoryManager,
this.ResizeRectangle.Width,
sourceRectangle.Width);
this.verticalWeights = this.PrecomputeWeights(
source.MemoryManager,
this.ResizeRectangle.Height,
sourceRectangle.Height);
}
}
/// <inheritdoc/>
@ -124,7 +292,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
// First process the columns. Since we are not using multiple threads startY and endY
// are the upper and lower bounds of the source rectangle.
// TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed!
using (Buffer2D<Vector4> firstPassPixels = this.MemoryManager.Allocate2D<Vector4>(width, source.Height))
using (Buffer2D<Vector4> firstPassPixels = source.MemoryManager.Allocate2D<Vector4>(width, source.Height))
{
firstPassPixels.Buffer.Clear();
@ -145,7 +313,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(tempRowSpan, sourceX);
}
}
@ -153,7 +321,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(tempRowSpan, sourceX);
}
}
@ -166,8 +334,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)
@ -196,5 +364,17 @@ 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);
// TODO: An exception in the processing chain can leave these buffers undisposed. We should consider making image processors IDisposable!
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;
}
}

6
src/ImageSharp/Processing/Transforms/Pad.cs

@ -1,10 +1,8 @@
// 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;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp
@ -25,11 +23,11 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Pad<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
ResizeOptions options = new ResizeOptions
var options = new ResizeOptions
{
Size = new Size(width, height),
Mode = ResizeMode.BoxPad,
Sampler = new NearestNeighborResampler()
Sampler = KnownResamplers.NearestNeighbor
};
return Resize(source, options);

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.GetCurrentSize()));
/// <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>(source.MemoryManager, sampler, width, height, targetRectangle) { Compand = compand }, sourceRectangle));
});
}
=> source.ApplyProcessor(new ResizeProcessor<TPixel>(sampler, width, height, source.GetCurrentSize(), 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>(source.MemoryManager, sampler, width, height, targetRectangle) { Compand = compand }));
});
}
=> source.ApplyProcessor(new ResizeProcessor<TPixel>(sampler, width, height, source.GetCurrentSize(), 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.GetCurrentSize()));
}
}

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

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);
=> source.ApplyProcessor(new AffineTransformProcessor<TPixel>(matrix, sampler, source.GetCurrentSize()));
/// <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);
=> source.ApplyProcessor(new ProjectiveTransformProcessor<TPixel>(matrix, sampler, source.GetCurrentSize()));
/// <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);

47
tests/ImageSharp.Tests/FakeImageOperationsProvider.cs

@ -1,15 +1,12 @@
// 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.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Tests
@ -26,13 +23,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>
@ -42,36 +39,36 @@ namespace SixLabors.ImageSharp.Tests
return op;
}
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 MemoryManager MemoryManager => this.Source.GetConfiguration().MemoryManager;
public Image<TPixel> Apply()
{
return source;
return this.Source;
}
public Size GetCurrentSize()
{
return this.Source.Size();
}
public IImageProcessingContext<TPixel> ApplyProcessor(IImageProcessor<TPixel> processor, Rectangle rectangle)
{
applied.Add(new AppliedOpperation
this.Applied.Add(new AppliedOperation
{
Processor = processor,
Rectangle = rectangle
@ -81,16 +78,14 @@ 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 MemoryManager MemoryManager => this.source.GetConfiguration().MemoryManager;
public struct AppliedOpperation
public struct AppliedOperation
{
public Rectangle? Rectangle { get; set; }
public IImageProcessor<TPixel> Processor { get; set; }

58
tests/ImageSharp.Tests/Image/ImageProcessingContextTests.cs

@ -0,0 +1,58 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
public class ImageProcessingContextTests
{
[Fact]
public void MutatedSizeIsAccuratePerOperation()
{
var x500 = new Size(500, 500);
var x400 = new Size(400, 400);
var x300 = new Size(300, 300);
var x200 = new Size(200, 200);
var x100 = new Size(100, 100);
using (var image = new Image<Rgba32>(500, 500))
{
image.Mutate(x =>
x.AssertSize(x500)
.Resize(x400).AssertSize(x400)
.Resize(x300).AssertSize(x300)
.Resize(x200).AssertSize(x200)
.Resize(x100).AssertSize(x100));
}
}
[Fact]
public void ClonedSizeIsAccuratePerOperation()
{
var x500 = new Size(500, 500);
var x400 = new Size(400, 400);
var x300 = new Size(300, 300);
var x200 = new Size(200, 200);
var x100 = new Size(100, 100);
using (var image = new Image<Rgba32>(500, 500))
{
image.Clone(x =>
x.AssertSize(x500)
.Resize(x400).AssertSize(x400)
.Resize(x300).AssertSize(x300)
.Resize(x200).AssertSize(x200)
.Resize(x100).AssertSize(x100));
}
}
}
public static class SizeAssertationExtensions
{
public static IImageProcessingContext<Rgba32> AssertSize(this IImageProcessingContext<Rgba32> context, Size size)
{
Assert.Equal(size, context.GetCurrentSize());
return context;
}
}
}

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()

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

@ -6,13 +6,12 @@ using System.IO;
using System.Text;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors;
using Xunit;
using SixLabors.Primitives;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
using SixLabors.ImageSharp.Memory;
public class ResizeProfilingBenchmarks : MeasureFixture
{
public ResizeProfilingBenchmarks(ITestOutputHelper output)
@ -40,9 +39,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
// [Fact]
public void PrintWeightsData()
{
var proc = new ResizeProcessor<Rgba32>(Configuration.Default.MemoryManager, new BicubicResampler(), 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(Configuration.Default.MemoryManager, proc.Width, size.Width);
var bld = new StringBuilder();

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

@ -1,20 +1,30 @@
// 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;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{
public class PadTest : BaseImageOperationsExtensionTest
{
#pragma warning disable xUnit1004 // Test methods should not be skipped
[Fact(Skip = "Skip this is a helper around resize, skip until resize can be refactord")]
#pragma warning restore xUnit1004 // Test methods should not be skipped
public void Pad_width_height_ResizeProcessorWithCorrectOPtionsSet()
[Fact]
public void PadWidthHeightResizeProcessorWithCorrectOptionsSet()
{
throw new NotImplementedException("Write test here");
int width = 500;
int height = 565;
IResampler sampler = KnownResamplers.NearestNeighbor;
this.operations.Pad(width, height);
ResizeProcessor<Rgba32> resizeProcessor = this.Verify<ResizeProcessor<Rgba32>>();
Assert.Equal(width, resizeProcessor.Width);
Assert.Equal(height, resizeProcessor.Height);
Assert.Equal(sampler, resizeProcessor.Sampler);
}
}
}

84
tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs

@ -1,23 +1,91 @@
// 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.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{
using SixLabors.ImageSharp.Processing.Processors;
public class ResizeTests : BaseImageOperationsExtensionTest
{
#pragma warning disable xUnit1004 // Test methods should not be skipped
[Fact(Skip = "Skip resize tests as they need refactoring to be simpler and just pass data into the resize processor.")]
#pragma warning restore xUnit1004 // Test methods should not be skipped
public void TestMissing()
[Fact]
public void ResizeWidthAndHeight()
{
int width = 50;
int height = 100;
this.operations.Resize(width, height);
ResizeProcessor<Rgba32> resizeProcessor = this.Verify<ResizeProcessor<Rgba32>>();
Assert.Equal(width, resizeProcessor.Width);
Assert.Equal(height, resizeProcessor.Height);
}
[Fact]
public void ResizeWidthAndHeightAndSampler()
{
int width = 50;
int height = 100;
IResampler sampler = KnownResamplers.Lanczos3;
this.operations.Resize(width, height, sampler);
ResizeProcessor<Rgba32> resizeProcessor = this.Verify<ResizeProcessor<Rgba32>>();
Assert.Equal(width, resizeProcessor.Width);
Assert.Equal(height, resizeProcessor.Height);
Assert.Equal(sampler, resizeProcessor.Sampler);
}
[Fact]
public void ResizeWidthAndHeightAndSamplerAndCompand()
{
//
throw new NotImplementedException("Write test here");
int width = 50;
int height = 100;
IResampler sampler = KnownResamplers.Lanczos3;
bool compand = true;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
this.operations.Resize(width, height, sampler, compand);
ResizeProcessor<Rgba32> resizeProcessor = this.Verify<ResizeProcessor<Rgba32>>();
Assert.Equal(width, resizeProcessor.Width);
Assert.Equal(height, resizeProcessor.Height);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand);
}
[Fact]
public void ResizeWithOptions()
{
int width = 50;
int height = 100;
IResampler sampler = KnownResamplers.Lanczos3;
bool compand = true;
ResizeMode mode = ResizeMode.Stretch;
var resizeOptions = new ResizeOptions
{
Size = new Size(width, height),
Sampler = sampler,
Compand = compand,
Mode = mode
};
this.operations.Resize(resizeOptions);
ResizeProcessor<Rgba32> resizeProcessor = this.Verify<ResizeProcessor<Rgba32>>();
Assert.Equal(width, resizeProcessor.Width);
Assert.Equal(height, resizeProcessor.Height);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand);
// Ensure options are not altered.
Assert.Equal(width, resizeOptions.Size.Width);
Assert.Equal(height, resizeOptions.Size.Height);
Assert.Equal(sampler, resizeOptions.Sampler);
Assert.Equal(compand, resizeOptions.Compand);
Assert.Equal(mode, resizeOptions.Mode);
}
}
}
Loading…
Cancel
Save