Browse Source

Implement IDisposable in IImageProcessor<TPixel> instances. (#990)

* Implement IDisposable and ensure inheritance calls base

* add ImageProcessingContextTests and move some other test classes

* loosen up tests and leave TODO notes
af/merge-core
James Jackson-South 7 years ago
committed by Anton Firsov
parent
commit
b173a70a00
  1. 19
      src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs
  2. 0
      src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs
  3. 21
      src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
  4. 9
      src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
  5. 9
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs
  6. 12
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  7. 9
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs
  8. 9
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
  9. 7
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
  10. 4
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  11. 5
      src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs
  12. 1
      src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs
  13. 0
      src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs
  14. 9
      src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs
  15. 9
      src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs
  16. 19
      src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs
  17. 2
      src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs
  18. 3
      src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
  19. 30
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  20. 5
      src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs
  21. 1
      tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs
  22. 1
      tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs
  23. 1
      tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs
  24. 1
      tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs
  25. 2
      tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs
  26. 1
      tests/ImageSharp.Tests/Drawing/Text/DrawText.cs
  27. 3
      tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs
  28. 9
      tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs
  29. 2
      tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs
  30. 2
      tests/ImageSharp.Tests/Processing/ImageOperationTests.cs
  31. 171
      tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs
  32. 18
      tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs

19
src/ImageSharp/Processing/DefaultImageProcessorContext.cs → src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Advanced;
@ -67,16 +67,25 @@ namespace SixLabors.ImageSharp.Processing
// This will only work if the first processor applied is the cloning one thus
// realistically for this optimization to work the resize must the first processor
// applied any only up processors will take the double data path.
if (processor.CreatePixelSpecificProcessor(this.source, rectangle) is ICloningImageProcessor<TPixel> cloningImageProcessor)
using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.source, rectangle))
{
this.destination = cloningImageProcessor.CloneAndApply();
return this;
// TODO: if 'specificProcessor' is not an ICloningImageProcessor<TPixel> we are unnecessarily disposing and recreating it.
// This should be solved in a future refactor.
if (specificProcessor is ICloningImageProcessor<TPixel> cloningImageProcessor)
{
this.destination = cloningImageProcessor.CloneAndApply();
return this;
}
}
this.destination = this.source.Clone();
}
processor.CreatePixelSpecificProcessor(this.destination, rectangle).Apply();
using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.destination, rectangle))
{
specificProcessor.Apply();
}
return this;
}

0
src/ImageSharp/Processing/IInternalImageProcessingContext.cs → src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs

21
src/ImageSharp/Processing/Processors/CloningImageProcessor.cs → src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs

@ -15,6 +15,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
internal abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="CloningImageProcessor{TPixel}"/> class.
/// </summary>
@ -54,6 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
Configuration configuration = this.Source.GetConfiguration();
this.BeforeImageApply(clone);
for (int i = 0; i < this.Source.Frames.Count; i++)
@ -97,6 +100,12 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
}
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(true);
}
/// <summary>
/// Generates a deep clone of the source image that operations should be applied to.
/// </summary>
@ -144,5 +153,17 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected virtual void AfterImageApply(Image<TPixel> destination)
{
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose managed and unmanaged objects.</param>
protected virtual void Dispose(bool disposing)
{
if (!this.isDisposed)
{
this.isDisposed = true;
}
}
}
}

9
src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs

@ -42,8 +42,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public DenseMatrix<float> KernelY { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) =>
new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle).Apply(source);
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using (var processor = new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
}
/// <summary>
/// Create a 1 dimensional Box kernel.

9
src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs

@ -56,10 +56,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
}
base.BeforeImageApply();
}
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source)
=> new Convolution2DProcessor<TPixel>(this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle).Apply(source);
{
using (var processor = new Convolution2DProcessor<TPixel>(this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
}
}
}

12
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs

@ -47,6 +47,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
}
base.BeforeImageApply();
}
/// <inheritdoc />
@ -68,7 +70,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// we need a clean copy for each pass to start from
using (ImageFrame<TPixel> cleanCopy = source.Clone())
{
new ConvolutionProcessor<TPixel>(kernels[0], true, this.Source, this.SourceRectangle).Apply(source);
using (var processor = new ConvolutionProcessor<TPixel>(kernels[0], true, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
if (kernels.Length == 1)
{
@ -97,7 +102,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
using (ImageFrame<TPixel> pass = cleanCopy.Clone())
{
new ConvolutionProcessor<TPixel>(kernels[i], true, this.Source, this.SourceRectangle).Apply(pass);
using (var processor = new ConvolutionProcessor<TPixel>(kernels[i], true, this.Source, this.SourceRectangle))
{
processor.Apply(pass);
}
Buffer2D<TPixel> passPixels = pass.PixelBuffer;
Buffer2D<TPixel> targetPixels = source.PixelBuffer;

9
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs

@ -43,10 +43,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
}
base.BeforeImageApply();
}
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
=> new ConvolutionProcessor<TPixel>(this.KernelXY, true, this.Source, this.SourceRectangle).Apply(source);
{
using (var processor = new ConvolutionProcessor<TPixel>(this.KernelXY, true, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
}
}
}

9
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs

@ -39,7 +39,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public DenseMatrix<float> KernelY { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) =>
new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle).Apply(source);
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using (var processor = new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
}
}
}

7
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs

@ -40,6 +40,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
=> new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle).Apply(source);
{
using (var processor = new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
}
}
}

4
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs

@ -44,8 +44,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <inheritdoc/>
protected override void BeforeFrameApply(ImageFrame<TPixel> source)
{
base.BeforeFrameApply(source);
// Lazy init palette:
if (this.palette is null)
{
@ -64,6 +62,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
(Span<Vector4>)this.paletteVector,
PixelConversionModifiers.Scale);
}
base.BeforeFrameApply(source);
}
/// <summary>

5
src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs

@ -27,9 +27,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
}
/// <inheritdoc/>
protected override void AfterFrameApply(ImageFrame<TPixel> source)
protected override void AfterImageApply()
{
new VignetteProcessor(VeryDarkGreen).CreatePixelSpecificProcessor(this.Source, this.SourceRectangle).Apply();
new VignetteProcessor(VeryDarkGreen).Apply(this.Source, this.SourceRectangle);
base.AfterImageApply();
}
}
}

1
src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs

@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
{
new VignetteProcessor(VeryDarkOrange).Apply(this.Source, this.SourceRectangle);
new GlowProcessor(LightOrange, this.Source.Width / 4F).Apply(this.Source, this.SourceRectangle);
base.AfterImageApply();
}
}
}

0
src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs → src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs

9
src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@ -10,16 +11,16 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Implements an algorithm to alter the pixels of an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IImageProcessor<TPixel>
public interface IImageProcessor<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}"/>.
/// Applies the process to the specified portion of the specified <see cref="Image{TPixel}"/>.
/// </summary>
/// <exception cref="System.ArgumentNullException">
/// <exception cref="ArgumentNullException">
/// The target <see cref="Image{TPixel}"/> is null.
/// </exception>
/// <exception cref="System.ArgumentException">
/// <exception cref="ArgumentException">
/// The target <see cref="Rectangle"/> doesn't fit the dimension of the image.
/// </exception>
void Apply();

9
src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs

@ -10,8 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
{
public static void Apply(this IImageProcessor processor, Image source, Rectangle sourceRectangle)
{
var visitor = new ApplyVisitor(processor, sourceRectangle);
source.AcceptVisitor(visitor);
source.AcceptVisitor(new ApplyVisitor(processor, sourceRectangle));
}
private class ApplyVisitor : IImageVisitor
@ -29,8 +28,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
public void Visit<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
IImageProcessor<TPixel> processorImpl = this.processor.CreatePixelSpecificProcessor(image, this.sourceRectangle);
processorImpl.Apply();
using (IImageProcessor<TPixel> processorImpl = this.processor.CreatePixelSpecificProcessor(image, this.sourceRectangle))
{
processorImpl.Apply();
}
}
}
}

19
src/ImageSharp/Processing/Processors/ImageProcessor.cs → src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs

@ -15,6 +15,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
internal abstract class ImageProcessor<TPixel> : IImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="ImageProcessor{TPixel}"/> class.
/// </summary>
@ -92,6 +94,11 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
}
/// <inheritdoc/>
public virtual void Dispose()
{
}
/// <summary>
/// This method is called before the process is applied to prepare the processor.
/// </summary>
@ -128,5 +135,17 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected virtual void AfterImageApply()
{
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose managed and unmanaged objects.</param>
protected virtual void Dispose(bool disposing)
{
if (!this.isDisposed)
{
this.isDisposed = true;
}
}
}
}

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

@ -68,6 +68,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
default:
break;
}
base.BeforeImageApply();
}
/// <inheritdoc/>

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

@ -35,6 +35,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
Rectangle rectangle;
// TODO: This is clunky. We should add behavior enum to ExtractFrame.
// All frames have be the same size so we only need to calculate the correct dimensions for the first frame
using (var temp = new Image<TPixel>(this.Configuration, this.Source.Metadata.DeepClone(), new[] { this.Source.Frames.RootFrame.Clone() }))
{
@ -51,6 +52,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
new CropProcessor(rectangle, this.Source.Size()).Apply(this.Source, this.SourceRectangle);
base.BeforeImageApply();
}
/// <inheritdoc/>

30
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -24,12 +24,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly ResizeProcessor parameterSource;
private bool isDisposed;
// The following fields are not immutable but are optionally created on demand.
private ResizeKernelMap horizontalKernelMap;
private ResizeKernelMap verticalKernelMap;
private readonly ResizeProcessor parameterSource;
public ResizeProcessor(ResizeProcessor parameterSource, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{
@ -109,6 +110,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
sourceRectangle.Height,
memoryAllocator);
}
base.BeforeImageApply(destination);
}
/// <inheritdoc/>
@ -189,15 +192,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
}
protected override void AfterImageApply(Image<TPixel> destination)
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
base.AfterImageApply(destination);
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.horizontalKernelMap?.Dispose();
this.horizontalKernelMap = null;
this.verticalKernelMap?.Dispose();
this.verticalKernelMap = null;
}
// TODO: An exception in the processing chain can leave these buffers undisposed. We should consider making image processors IDisposable!
this.horizontalKernelMap?.Dispose();
this.horizontalKernelMap = null;
this.verticalKernelMap?.Dispose();
this.verticalKernelMap = null;
this.isDisposed = true;
base.Dispose(disposing);
}
}
}

5
src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs

@ -25,6 +25,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <inheritdoc/>
protected override void AfterImageApply(Image<TPixel> destination)
=> TransformProcessorHelpers.UpdateDimensionalMetadata(destination);
{
TransformProcessorHelpers.UpdateDimensionalMetadata(destination);
base.AfterImageApply(destination);
}
}
}

1
tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.Shapes;
using Xunit;

1
tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.Shapes;
using Xunit;

1
tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.Shapes;
using Xunit;

1
tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.Shapes;
using Xunit;

2
tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs

@ -5,6 +5,8 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing.Paths

1
tests/ImageSharp.Tests/Drawing/Text/DrawText.cs

@ -5,6 +5,7 @@ using System.Numerics;
using SixLabors.Fonts;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Text;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.Primitives;
using Xunit;

3
tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs → tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs

@ -4,9 +4,10 @@
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests
namespace SixLabors.ImageSharp.Tests.Processing
{
public abstract class BaseImageOperationsExtensionTest
{

9
tests/ImageSharp.Tests/FakeImageOperationsProvider.cs → tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs

@ -3,14 +3,15 @@
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Tests
namespace SixLabors.ImageSharp.Tests.Processing
{
internal class FakeImageOperationsProvider : IImageProcessingContextFactory
{
@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests
public bool HasCreated<TPixel>(Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return Created(source).Any();
return this.Created(source).Any();
}
public IEnumerable<FakeImageOperations<TPixel>> Created<TPixel>(Image<TPixel> source) where TPixel : struct, IPixel<TPixel>
{
@ -29,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests
public IEnumerable<FakeImageOperations<TPixel>.AppliedOperation> AppliedOperations<TPixel>(Image<TPixel> source) where TPixel : struct, IPixel<TPixel>
{
return Created(source)
return this.Created(source)
.SelectMany(x => x.Applied);
}

2
tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Tests.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests

2
tests/ImageSharp.Tests/ImageOperationTests.cs → tests/ImageSharp.Tests/Processing/ImageOperationTests.cs

@ -12,7 +12,7 @@ using SixLabors.ImageSharp.Processing.Processors;
using Xunit;
namespace SixLabors.ImageSharp.Tests
namespace SixLabors.ImageSharp.Tests.Processing
{
/// <summary>
/// Tests the configuration class.

171
tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs

@ -0,0 +1,171 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using Moq;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing
{
/// <summary>
/// Contains test cases for default <see cref="IImageProcessingContext"/> implementation.
/// </summary>
public class ImageProcessingContextTests
{
private readonly Image image = new Image<Rgba32>(10, 10);
private readonly Mock<IImageProcessor> processorDefinition;
private readonly Mock<IImageProcessor<Rgba32>> regularProcessorImpl;
private readonly Mock<ICloningImageProcessor<Rgba32>> cloningProcessorImpl;
private static readonly Rectangle Bounds = new Rectangle(3, 3, 5, 5);
public ImageProcessingContextTests()
{
this.processorDefinition = new Mock<IImageProcessor>();
this.regularProcessorImpl = new Mock<IImageProcessor<Rgba32>>();
this.cloningProcessorImpl = new Mock<ICloningImageProcessor<Rgba32>>();
}
// bool throwException, bool useBounds
public static readonly TheoryData<bool, bool> ProcessorTestData = new TheoryData<bool, bool>()
{
{ false, false },
{ false, true },
{ true, false },
{ true, true }
};
[Theory]
[MemberData(nameof(ProcessorTestData))]
public void Mutate_RegularProcessor(bool throwException, bool useBounds)
{
this.SetupRegularProcessor(throwException);
if (throwException)
{
Assert.Throws<ImageProcessingException>(() => this.MutateApply(useBounds));
}
else
{
this.MutateApply(useBounds);
}
this.regularProcessorImpl.Verify(p => p.Apply(), Times.Once());
this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once());
}
[Theory]
[MemberData(nameof(ProcessorTestData))]
public void Clone_RegularProcessor(bool throwException, bool useBounds)
{
this.SetupRegularProcessor(throwException);
if (throwException)
{
Assert.Throws<ImageProcessingException>(() => this.CloneApply(useBounds));
}
else
{
this.CloneApply(useBounds);
}
// TODO: This should be Times.Once(). See comments in DefaultImageProcessingContext<T>.ApplyProcessor()
this.regularProcessorImpl.Verify(p => p.Apply(), Times.AtLeast(1));
this.regularProcessorImpl.Verify(p => p.Dispose(), Times.AtLeast(1));
}
[Theory]
[MemberData(nameof(ProcessorTestData))]
public void Mutate_CloningProcessor(bool throwException, bool useBounds)
{
this.SetupCloningProcessor(throwException);
if (throwException)
{
Assert.Throws<ImageProcessingException>(() => this.MutateApply(useBounds));
}
else
{
this.MutateApply(useBounds);
}
this.cloningProcessorImpl.Verify(p => p.Apply(), Times.Once());
this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once());
}
[Theory]
[MemberData(nameof(ProcessorTestData))]
public void Clone_CloningProcessor(bool throwException, bool useBounds)
{
this.SetupCloningProcessor(throwException);
if (throwException)
{
Assert.Throws<ImageProcessingException>(() => this.CloneApply(useBounds));
}
else
{
this.CloneApply(useBounds);
}
this.cloningProcessorImpl.Verify(p => p.CloneAndApply(), Times.Once());
this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once());
}
private void MutateApply(bool useBounds)
{
if (useBounds)
{
this.image.Mutate(c => c.ApplyProcessor(this.processorDefinition.Object, Bounds));
}
else
{
this.image.Mutate(c => c.ApplyProcessor(this.processorDefinition.Object));
}
}
private void CloneApply(bool useBounds)
{
if (useBounds)
{
this.image.Clone(c => c.ApplyProcessor(this.processorDefinition.Object, Bounds)).Dispose();
}
else
{
this.image.Clone(c => c.ApplyProcessor(this.processorDefinition.Object)).Dispose();
}
}
private void SetupRegularProcessor(bool throwsException)
{
if (throwsException)
{
this.regularProcessorImpl.Setup(p => p.Apply()).Throws(new ImageProcessingException("Test"));
}
this.processorDefinition
.Setup(p => p.CreatePixelSpecificProcessor(It.IsAny<Image<Rgba32>>(), It.IsAny<Rectangle>()))
.Returns(this.regularProcessorImpl.Object);
}
private void SetupCloningProcessor(bool throwsException)
{
if (throwsException)
{
this.cloningProcessorImpl.Setup(p => p.Apply()).Throws(new ImageProcessingException("Test"));
this.cloningProcessorImpl.Setup(p => p.CloneAndApply()).Throws(new ImageProcessingException("Test"));
}
this.processorDefinition
.Setup(p => p.CreatePixelSpecificProcessor(It.IsAny<Image<Rgba32>>(), It.IsAny<Rectangle>()))
.Returns(this.cloningProcessorImpl.Object);
}
}
}

18
tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs

@ -59,16 +59,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
using (var image = new Image<Rgb24>(1, 1))
{
var definition = new BokehBlurProcessor(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma);
var processor = (BokehBlurProcessor<Rgb24>)definition.CreatePixelSpecificProcessor(image, image.Bounds());
Assert.Equal(components.Count, processor.Kernels.Count);
foreach ((Complex64[] a, Complex64[] b) in components.Zip(processor.Kernels, (a, b) => (a, b)))
using (var processor = (BokehBlurProcessor<Rgb24>)definition.CreatePixelSpecificProcessor(image, image.Bounds()))
{
Span<Complex64> spanA = a.AsSpan(), spanB = b.AsSpan();
Assert.Equal(spanA.Length, spanB.Length);
for (int i = 0; i < spanA.Length; i++)
Assert.Equal(components.Count, processor.Kernels.Count);
foreach ((Complex64[] a, Complex64[] b) in components.Zip(processor.Kernels, (a, b) => (a, b)))
{
Assert.True(Math.Abs(Math.Abs(spanA[i].Real) - Math.Abs(spanB[i].Real)) < 0.0001f);
Assert.True(Math.Abs(Math.Abs(spanA[i].Imaginary) - Math.Abs(spanB[i].Imaginary)) < 0.0001f);
Span<Complex64> spanA = a.AsSpan(), spanB = b.AsSpan();
Assert.Equal(spanA.Length, spanB.Length);
for (int i = 0; i < spanA.Length; i++)
{
Assert.True(Math.Abs(Math.Abs(spanA[i].Real) - Math.Abs(spanB[i].Real)) < 0.0001f);
Assert.True(Math.Abs(Math.Abs(spanA[i].Imaginary) - Math.Abs(spanB[i].Imaginary)) < 0.0001f);
}
}
}
}

Loading…
Cancel
Save