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. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Advanced; 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 // 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 // realistically for this optimization to work the resize must the first processor
// applied any only up processors will take the double data path. // 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(); // TODO: if 'specificProcessor' is not an ICloningImageProcessor<TPixel> we are unnecessarily disposing and recreating it.
return this; // 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(); this.destination = this.source.Clone();
} }
processor.CreatePixelSpecificProcessor(this.destination, rectangle).Apply(); using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.destination, rectangle))
{
specificProcessor.Apply();
}
return this; 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> internal abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CloningImageProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="CloningImageProcessor{TPixel}"/> class.
/// </summary> /// </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."); 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); this.BeforeImageApply(clone);
for (int i = 0; i < this.Source.Frames.Count; i++) 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> /// <summary>
/// Generates a deep clone of the source image that operations should be applied to. /// Generates a deep clone of the source image that operations should be applied to.
/// </summary> /// </summary>
@ -144,5 +153,17 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected virtual void AfterImageApply(Image<TPixel> destination) 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; } public DenseMatrix<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) => 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);
}
}
/// <summary> /// <summary>
/// Create a 1 dimensional Box kernel. /// 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); new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
} }
base.BeforeImageApply();
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source) 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); new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
} }
base.BeforeImageApply();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -68,7 +70,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// we need a clean copy for each pass to start from // we need a clean copy for each pass to start from
using (ImageFrame<TPixel> cleanCopy = source.Clone()) 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) if (kernels.Length == 1)
{ {
@ -97,7 +102,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
using (ImageFrame<TPixel> pass = cleanCopy.Clone()) 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> passPixels = pass.PixelBuffer;
Buffer2D<TPixel> targetPixels = source.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); new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
} }
base.BeforeImageApply();
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) 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; } public DenseMatrix<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) => 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);
}
}
} }
} }

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

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

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

@ -27,9 +27,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
} }
/// <inheritdoc/> /// <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 VignetteProcessor(VeryDarkOrange).Apply(this.Source, this.SourceRectangle);
new GlowProcessor(LightOrange, this.Source.Width / 4F).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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -10,16 +11,16 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Implements an algorithm to alter the pixels of an image. /// Implements an algorithm to alter the pixels of an image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IImageProcessor<TPixel> public interface IImageProcessor<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <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> /// </summary>
/// <exception cref="System.ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// The target <see cref="Image{TPixel}"/> is null. /// The target <see cref="Image{TPixel}"/> is null.
/// </exception> /// </exception>
/// <exception cref="System.ArgumentException"> /// <exception cref="ArgumentException">
/// The target <see cref="Rectangle"/> doesn't fit the dimension of the image. /// The target <see cref="Rectangle"/> doesn't fit the dimension of the image.
/// </exception> /// </exception>
void Apply(); 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) public static void Apply(this IImageProcessor processor, Image source, Rectangle sourceRectangle)
{ {
var visitor = new ApplyVisitor(processor, sourceRectangle); source.AcceptVisitor(new ApplyVisitor(processor, sourceRectangle));
source.AcceptVisitor(visitor);
} }
private class ApplyVisitor : IImageVisitor private class ApplyVisitor : IImageVisitor
@ -29,8 +28,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
public void Visit<TPixel>(Image<TPixel> image) public void Visit<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
IImageProcessor<TPixel> processorImpl = this.processor.CreatePixelSpecificProcessor(image, this.sourceRectangle); using (IImageProcessor<TPixel> processorImpl = this.processor.CreatePixelSpecificProcessor(image, this.sourceRectangle))
processorImpl.Apply(); {
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> internal abstract class ImageProcessor<TPixel> : IImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="ImageProcessor{TPixel}"/> class.
/// </summary> /// </summary>
@ -92,6 +94,11 @@ namespace SixLabors.ImageSharp.Processing.Processors
} }
} }
/// <inheritdoc/>
public virtual void Dispose()
{
}
/// <summary> /// <summary>
/// This method is called before the process is applied to prepare the processor. /// This method is called before the process is applied to prepare the processor.
/// </summary> /// </summary>
@ -128,5 +135,17 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected virtual void AfterImageApply() 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: default:
break; break;
} }
base.BeforeImageApply();
} }
/// <inheritdoc/> /// <inheritdoc/>

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

@ -35,6 +35,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
Rectangle rectangle; 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 // 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() })) 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); new CropProcessor(rectangle, this.Source.Size()).Apply(this.Source, this.SourceRectangle);
base.BeforeImageApply();
} }
/// <inheritdoc/> /// <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> internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<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. // The following fields are not immutable but are optionally created on demand.
private ResizeKernelMap horizontalKernelMap; private ResizeKernelMap horizontalKernelMap;
private ResizeKernelMap verticalKernelMap; private ResizeKernelMap verticalKernelMap;
private readonly ResizeProcessor parameterSource;
public ResizeProcessor(ResizeProcessor parameterSource, Image<TPixel> source, Rectangle sourceRectangle) public ResizeProcessor(ResizeProcessor parameterSource, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle) : base(source, sourceRectangle)
{ {
@ -109,6 +110,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
sourceRectangle.Height, sourceRectangle.Height,
memoryAllocator); memoryAllocator);
} }
base.BeforeImageApply(destination);
} }
/// <inheritdoc/> /// <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.isDisposed = true;
this.horizontalKernelMap?.Dispose(); base.Dispose(disposing);
this.horizontalKernelMap = null;
this.verticalKernelMap?.Dispose();
this.verticalKernelMap = null;
} }
} }
} }

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

@ -25,6 +25,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <inheritdoc/> /// <inheritdoc/>
protected override void AfterImageApply(Image<TPixel> destination) 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.Primitives;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing; using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.Shapes; using SixLabors.Shapes;
using Xunit; 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.Primitives;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing; using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.Shapes; using SixLabors.Shapes;
using Xunit; 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.Primitives;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing; using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.Shapes; using SixLabors.Shapes;
using Xunit; 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.Primitives;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing; using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.Shapes; using SixLabors.Shapes;
using Xunit; 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.Primitives;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing; using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing.Paths 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.Fonts;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Text; using SixLabors.ImageSharp.Processing.Processors.Text;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.Primitives; using SixLabors.Primitives;
using Xunit; 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.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.Primitives; using SixLabors.Primitives;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests.Processing
{ {
public abstract class BaseImageOperationsExtensionTest 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.Collections.Generic;
using System.Linq; using System.Linq;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests.Processing
{ {
internal class FakeImageOperationsProvider : IImageProcessingContextFactory internal class FakeImageOperationsProvider : IImageProcessingContextFactory
{ {
@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests
public bool HasCreated<TPixel>(Image<TPixel> source) public bool HasCreated<TPixel>(Image<TPixel> source)
where TPixel : struct, IPixel<TPixel> 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> 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> 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); .SelectMany(x => x.Applied);
} }

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

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Tests.Processing;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests 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; using Xunit;
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests.Processing
{ {
/// <summary> /// <summary>
/// Tests the configuration class. /// 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)) using (var image = new Image<Rgb24>(1, 1))
{ {
var definition = new BokehBlurProcessor(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma); var definition = new BokehBlurProcessor(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma);
var processor = (BokehBlurProcessor<Rgb24>)definition.CreatePixelSpecificProcessor(image, image.Bounds()); using (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)))
{ {
Span<Complex64> spanA = a.AsSpan(), spanB = b.AsSpan(); Assert.Equal(components.Count, processor.Kernels.Count);
Assert.Equal(spanA.Length, spanB.Length); foreach ((Complex64[] a, Complex64[] b) in components.Zip(processor.Kernels, (a, b) => (a, b)))
for (int i = 0; i < spanA.Length; i++)
{ {
Assert.True(Math.Abs(Math.Abs(spanA[i].Real) - Math.Abs(spanB[i].Real)) < 0.0001f); Span<Complex64> spanA = a.AsSpan(), spanB = b.AsSpan();
Assert.True(Math.Abs(Math.Abs(spanA[i].Imaginary) - Math.Abs(spanB[i].Imaginary)) < 0.0001f); 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