Browse Source

Merge branch 'master' into js/simplify-jpeg-encoder

pull/1574/head
James Jackson-South 6 years ago
parent
commit
3d8b4cf91c
  1. 6
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  2. 4
      src/ImageSharp/Advanced/IImageVisitor.cs
  3. 6
      src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs
  4. 42
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  5. 5
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  6. 5
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  7. 30
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  8. 48
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  9. 5
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  10. 5
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  11. 23
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  12. 100
      src/ImageSharp/Formats/Gif/ImageExtensions.cs
  13. 7
      src/ImageSharp/Formats/IImageDecoder.cs
  14. 20
      src/ImageSharp/Formats/IImageDecoderInternals.cs
  15. 4
      src/ImageSharp/Formats/IImageEncoder.cs
  16. 25
      src/ImageSharp/Formats/IImageEncoderInternals.cs
  17. 4
      src/ImageSharp/Formats/IImageInfoDetector.cs
  18. 153
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  19. 61
      src/ImageSharp/Formats/ImageEncoderUtilities.cs
  20. 539
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  21. 93
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  22. 17
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  23. 5
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
  24. 100
      src/ImageSharp/Formats/Jpeg/ImageExtensions.cs
  25. 56
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  26. 31
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  27. 20
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  28. 26
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  29. 100
      src/ImageSharp/Formats/Png/ImageExtensions.cs
  30. 49
      src/ImageSharp/Formats/Png/PngDecoder.cs
  31. 6
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  32. 9
      src/ImageSharp/Formats/Png/PngEncoder.cs
  33. 30
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  34. 100
      src/ImageSharp/Formats/Tga/ImageExtensions.cs
  35. 48
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  36. 5
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  37. 5
      src/ImageSharp/Formats/Tga/TgaEncoder.cs
  38. 30
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  39. 2
      src/ImageSharp/IO/BufferedReadStream.cs
  40. 22
      src/ImageSharp/Image.Decode.cs
  41. 221
      src/ImageSharp/Image.FromFile.cs
  42. 103
      src/ImageSharp/Image.FromStream.cs
  43. 13
      src/ImageSharp/Image.cs
  44. 19
      src/ImageSharp/ImageExtensions.cs
  45. 15
      src/ImageSharp/ImageSharp.csproj
  46. 5
      src/ImageSharp/Image{TPixel}.cs
  47. 61
      src/ImageSharp/Processing/EdgeDetectionOperators.cs
  48. 249
      src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs
  49. 63
      src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs
  50. 42
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs
  51. 43
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs
  52. 42
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs
  53. 32
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  54. 25
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs
  55. 28
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs
  56. 31
      src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs
  57. 55
      src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs
  58. 103
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs
  59. 163
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs
  60. 78
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs
  61. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs
  62. 25
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs
  63. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs
  64. 8
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs
  65. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs
  66. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs
  67. 23
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs
  68. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs
  69. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs
  70. 25
      src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs
  71. 25
      src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs
  72. 25
      src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs
  73. 25
      src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs
  74. 31
      src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs
  75. 31
      src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs
  76. 25
      src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs
  77. 31
      src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs
  78. 31
      src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs
  79. 2
      src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
  80. 22
      tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs
  81. 58
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  82. 28
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  83. 2
      tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs
  84. 16
      tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs
  85. 130
      tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs
  86. 2
      tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs
  87. 44
      tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
  88. 25
      tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
  89. 86
      tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs
  90. 2
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs
  91. 20
      tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
  92. 2
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  93. 198
      tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs
  94. 81
      tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs
  95. 116
      tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
  96. 10
      tests/ImageSharp.Tests/TestFileSystem.cs
  97. 41
      tests/ImageSharp.Tests/TestFormat.cs
  98. 12
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
  99. 6
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
  100. 6
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

6
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -73,9 +74,10 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary> /// </summary>
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="visitor">The image visitor.</param> /// <param name="visitor">The image visitor.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor) public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default)
=> source.AcceptAsync(visitor); => source.AcceptAsync(visitor, cancellationToken);
/// <summary> /// <summary>
/// Gets the configuration for the image. /// Gets the configuration for the image.

4
src/ImageSharp/Advanced/IImageVisitor.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -31,9 +32,10 @@ namespace SixLabors.ImageSharp.Advanced
/// Provides a pixel-specific implementation for a given operation. /// Provides a pixel-specific implementation for a given operation.
/// </summary> /// </summary>
/// <param name="image">The image.</param> /// <param name="image">The image.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam> /// <typeparam name="TPixel">The pixel type.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task VisitAsync<TPixel>(Image<TPixel> image) Task VisitAsync<TPixel>(Image<TPixel> image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;
} }
} }

6
src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
@ -32,5 +33,10 @@ namespace SixLabors.ImageSharp
: base(errorMessage, innerException) : base(errorMessage, innerException)
{ {
} }
internal InvalidImageContentException(Size size, InvalidMemoryOperationException memoryException)
: this($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {size.Width}x{size.Height}.", memoryException)
{
}
} }
} }

42
src/ImageSharp/Formats/Bmp/BmpDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -36,18 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this); var decoder = new BmpDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex);
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -55,46 +45,34 @@ namespace SixLabors.ImageSharp.Formats.Bmp
=> this.Decode<Rgba32>(configuration, stream); => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/> /// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream) public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this); var decoder = new BmpDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex);
}
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false); => await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/> /// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream) public IImageInfo Identify(Configuration configuration, Stream stream)
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream); return new BmpDecoderCore(configuration, this).Identify(configuration, stream);
return new BmpDecoderCore(configuration, this).Identify(bufferedStream);
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream) public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream); return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
return new BmpDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
} }
} }
} }

5
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -6,6 +6,7 @@ using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -118,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height); public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
/// <inheritdoc /> /// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
try try
@ -197,7 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
/// <inheritdoc /> /// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream) public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{ {
this.ReadImageHeaders(stream, out _, out _); this.ReadImageHeaders(stream, out _, out _);
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata); return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);

5
src/ImageSharp/Formats/Bmp/BmpEncoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -42,11 +43,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream) public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream); return encoder.EncodeAsync(image, stream, cancellationToken);
} }
} }
} }

30
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -5,6 +5,7 @@ using System;
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary> /// <summary>
/// Image encoder for writing an image to a stream as a Windows bitmap. /// Image encoder for writing an image to a stream as a Windows bitmap.
/// </summary> /// </summary>
internal sealed class BmpEncoderCore internal sealed class BmpEncoderCore : IImageEncoderInternals
{ {
/// <summary> /// <summary>
/// The amount to pad each row by. /// The amount to pad each row by.
@ -97,32 +98,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream) /// <param name="cancellationToken">The token to request cancellation.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{
if (stream.CanSeek)
{
this.Encode(image, stream);
}
else
{
using (var ms = new MemoryStream())
{
this.Encode(image, ms);
ms.Position = 0;
await ms.CopyToAsync(stream).ConfigureAwait(false);
}
}
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));

48
src/ImageSharp/Formats/Gif/GifDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -30,21 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new GifDecoderCore(configuration, this); var decoder = new GifDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -52,30 +39,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
=> this.Decode<Rgba32>(configuration, stream); => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/> /// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream) public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new GifDecoderCore(configuration, this); var decoder = new GifDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false); => await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/> /// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream) public IImageInfo Identify(Configuration configuration, Stream stream)
@ -85,18 +59,16 @@ namespace SixLabors.ImageSharp.Formats.Gif
var decoder = new GifDecoderCore(configuration, this); var decoder = new GifDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream); using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Identify(bufferedStream); return decoder.Identify(bufferedStream, default);
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream) public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this); var decoder = new GifDecoderCore(configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.IdentifyAsync(bufferedStream);
} }
} }
} }

5
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -6,6 +6,7 @@ using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -97,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator; private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator;
/// <inheritdoc /> /// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Image<TPixel> image = null; Image<TPixel> image = null;
@ -158,7 +159,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
/// <inheritdoc /> /// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream) public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{ {
try try
{ {

5
src/ImageSharp/Formats/Gif/GifEncoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -41,11 +42,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream) public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var encoder = new GifEncoderCore(image.GetConfiguration(), this); var encoder = new GifEncoderCore(image.GetConfiguration(), this);
return encoder.EncodeAsync(image, stream); return encoder.EncodeAsync(image, stream, cancellationToken);
} }
} }
} }

23
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -6,6 +6,7 @@ using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -18,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// Implements the GIF encoding protocol. /// Implements the GIF encoding protocol.
/// </summary> /// </summary>
internal sealed class GifEncoderCore internal sealed class GifEncoderCore : IImageEncoderInternals
{ {
/// <summary> /// <summary>
/// Used for allocating memory during processing operations. /// Used for allocating memory during processing operations.
@ -75,25 +76,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream) /// <param name="cancellationToken">The token to request cancellation.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{
using (var ms = new MemoryStream())
{
this.Encode(image, ms);
ms.Position = 0;
await ms.CopyToAsync(stream).ConfigureAwait(false);
}
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));

100
src/ImageSharp/Formats/Gif/ImageExtensions.cs

@ -1,100 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Gif;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Saves the image to the given stream with the gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsGif(this Image source, string path) => SaveAsGif(source, path, null);
/// <summary>
/// Saves the image to the given stream with the gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsGifAsync(this Image source, string path) => SaveAsGifAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsGif(this Image source, string path, GifEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsGif(this Image source, Stream stream) => SaveAsGif(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsGifAsync(this Image source, Stream stream) => SaveAsGifAsync(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) =>
source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance));
}
}

7
src/ImageSharp/Formats/IImageDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -38,9 +39,10 @@ namespace SixLabors.ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</param> /// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream) Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;
/// <summary> /// <summary>
@ -48,8 +50,9 @@ namespace SixLabors.ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="configuration">The configuration for the image.</param> /// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Task<Image> DecodeAsync(Configuration configuration, Stream stream); Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken);
} }
} }

20
src/ImageSharp/Formats/IImageDecoderInternals.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Threading;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -17,21 +18,36 @@ namespace SixLabors.ImageSharp.Formats
/// </summary> /// </summary>
Configuration Configuration { get; } Configuration Configuration { get; }
/// <summary>
/// Gets the dimensions of the image being decoded.
/// </summary>
Size Dimensions { get; }
/// <summary> /// <summary>
/// Decodes the image from the specified stream. /// Decodes the image from the specified stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param> /// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception> /// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>The decoded image.</returns> /// <returns>The decoded image.</returns>
Image<TPixel> Decode<TPixel>(BufferedReadStream stream) /// <remarks>
/// Cancellable synchronous method. In case of cancellation,
/// an <see cref="OperationCanceledException"/> shall be thrown which will be handled on the call site.
/// </remarks>
Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream. /// Reads the raw image information from the specified stream.
/// </summary> /// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param> /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="IImageInfo"/>.</returns> /// <returns>The <see cref="IImageInfo"/>.</returns>
IImageInfo Identify(BufferedReadStream stream); /// <remarks>
/// Cancellable synchronous method. In case of cancellation,
/// an <see cref="OperationCanceledException"/> shall be thrown which will be handled on the call site.
/// </remarks>
IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken);
} }
} }

4
src/ImageSharp/Formats/IImageEncoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -27,8 +28,9 @@ namespace SixLabors.ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream) Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;
} }
} }

25
src/ImageSharp/Formats/IImageEncoderInternals.cs

@ -0,0 +1,25 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Abstraction for shared internals for ***DecoderCore implementations to be used with <see cref="ImageEncoderUtilities"/>.
/// </summary>
internal interface IImageEncoderInternals
{
/// <summary>
/// Encodes the image.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="stream">The stream.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
}
}

4
src/ImageSharp/Formats/IImageInfoDetector.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SixLabors.ImageSharp.Formats namespace SixLabors.ImageSharp.Formats
@ -24,7 +25,8 @@ namespace SixLabors.ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="configuration">The configuration for the image.</param> /// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="PixelTypeInfo"/> object</returns> /// <returns>The <see cref="PixelTypeInfo"/> object</returns>
Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream); Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken);
} }
} }

153
src/ImageSharp/Formats/ImageDecoderUtilities.cs

@ -2,8 +2,11 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats namespace SixLabors.ImageSharp.Formats
@ -14,21 +17,159 @@ namespace SixLabors.ImageSharp.Formats
/// Reads the raw image information from the specified stream. /// Reads the raw image information from the specified stream.
/// </summary> /// </summary>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param> /// /// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception> /// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<IImageInfo> IdentifyAsync(this IImageDecoderInternals decoder, BufferedReadStream stream) public static Task<IImageInfo> IdentifyAsync(
=> Task.FromResult(decoder.Identify(stream)); this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
CancellationToken cancellationToken)
=> decoder.IdentifyAsync(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken);
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="tooLargeImageExceptionFactory">Factory method to handle <see cref="InvalidMemoryOperationException"/> as <see cref="InvalidImageContentException"/>.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<IImageInfo> IdentifyAsync(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> tooLargeImageExceptionFactory,
CancellationToken cancellationToken)
{
try
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
IImageInfo imageInfo = decoder.Identify(bufferedReadStream, cancellationToken);
return Task.FromResult(imageInfo);
}
catch (InvalidMemoryOperationException ex)
{
InvalidImageContentException invalidImageContentException = tooLargeImageExceptionFactory(ex, decoder.Dimensions);
return Task.FromException<IImageInfo>(invalidImageContentException);
}
catch (OperationCanceledException)
{
return Task.FromCanceled<IImageInfo>(cancellationToken);
}
catch (Exception ex)
{
return Task.FromException<IImageInfo>(ex);
}
}
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> DecodeAsync<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> =>
decoder.DecodeAsync<TPixel>(
configuration,
stream,
DefaultLargeImageExceptionFactory,
cancellationToken);
/// <summary> /// <summary>
/// Decodes the image from the specified stream. /// Decodes the image from the specified stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param> /// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="largeImageExceptionFactory">Factory method to handle <see cref="InvalidMemoryOperationException"/> as <see cref="InvalidImageContentException"/>.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> DecodeAsync<TPixel>(this IImageDecoderInternals decoder, BufferedReadStream stream) public static Task<Image<TPixel>> DecodeAsync<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(decoder.Decode<TPixel>(stream)); {
try
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
Image<TPixel> image = decoder.Decode<TPixel>(bufferedReadStream, cancellationToken);
return Task.FromResult(image);
}
catch (InvalidMemoryOperationException ex)
{
InvalidImageContentException invalidImageContentException = largeImageExceptionFactory(ex, decoder.Dimensions);
return Task.FromException<Image<TPixel>>(invalidImageContentException);
}
catch (OperationCanceledException)
{
return Task.FromCanceled<Image<TPixel>>(cancellationToken);
}
catch (Exception ex)
{
return Task.FromException<Image<TPixel>>(ex);
}
}
public static IImageInfo Identify(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream)
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
return decoder.Identify(bufferedReadStream, default);
}
catch (InvalidMemoryOperationException ex)
{
throw new InvalidImageContentException(decoder.Dimensions, ex);
}
}
public static Image<TPixel> Decode<TPixel>(this IImageDecoderInternals decoder, Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> decoder.Decode<TPixel>(configuration, stream, DefaultLargeImageExceptionFactory);
public static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory)
where TPixel : unmanaged, IPixel<TPixel>
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
return decoder.Decode<TPixel>(bufferedReadStream, default);
}
catch (InvalidMemoryOperationException ex)
{
throw largeImageExceptionFactory(ex, decoder.Dimensions);
}
}
private static InvalidImageContentException DefaultLargeImageExceptionFactory(
InvalidMemoryOperationException memoryOperationException,
Size dimensions) =>
new InvalidImageContentException(dimensions, memoryOperationException);
} }
} }

61
src/ImageSharp/Formats/ImageEncoderUtilities.cs

@ -0,0 +1,61 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
{
internal static class ImageEncoderUtilities
{
public static async Task EncodeAsync<TPixel>(
this IImageEncoderInternals encoder,
Image<TPixel> image,
Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Configuration configuration = image.GetConfiguration();
if (stream.CanSeek)
{
await DoEncodeAsync(stream).ConfigureAwait(false);
}
else
{
using var ms = new MemoryStream();
await DoEncodeAsync(ms);
ms.Position = 0;
await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken)
.ConfigureAwait(false);
}
Task DoEncodeAsync(Stream innerStream)
{
try
{
encoder.Encode(image, innerStream, cancellationToken);
return Task.CompletedTask;
}
catch (OperationCanceledException)
{
return Task.FromCanceled(cancellationToken);
}
catch (Exception ex)
{
return Task.FromException(ex);
}
}
}
public static void Encode<TPixel>(
this IImageEncoderInternals encoder,
Image<TPixel> image,
Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> encoder.Encode(image, stream, default);
}
}

539
src/ImageSharp/Formats/ImageExtensions.Save.cs

@ -0,0 +1,539 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// <auto-generated />
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Saves the image to the given stream with the Bmp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsBmp(this Image source, string path) => SaveAsBmp(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Bmp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsBmpAsync(this Image source, string path) => SaveAsBmpAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Bmp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsBmpAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsBmpAsync(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Bmp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsBmp(this Image source, string path, BmpEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Bmp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Bmp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsBmp(this Image source, Stream stream)
=> SaveAsBmp(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the Bmp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsBmpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsBmpAsync(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Bmp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Bmp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsGif(this Image source, string path) => SaveAsGif(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsGifAsync(this Image source, string path) => SaveAsGifAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsGifAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsGifAsync(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsGif(this Image source, string path, GifEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsGif(this Image source, Stream stream)
=> SaveAsGif(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the Gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsGifAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsGifAsync(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Gif format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsJpeg(this Image source, string path) => SaveAsJpeg(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsJpegAsync(this Image source, string path) => SaveAsJpegAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsJpegAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsJpegAsync(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsJpeg(this Image source, string path, JpegEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsJpeg(this Image source, Stream stream)
=> SaveAsJpeg(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the Jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsJpegAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsJpegAsync(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsPng(this Image source, string path) => SaveAsPng(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPngAsync(this Image source, string path) => SaveAsPngAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPngAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsPngAsync(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsPng(this Image source, string path, PngEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsPng(this Image source, Stream stream)
=> SaveAsPng(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the Png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPngAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsPngAsync(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsTga(this Image source, string path) => SaveAsTga(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTgaAsync(this Image source, string path) => SaveAsTgaAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTgaAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsTgaAsync(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsTga(this Image source, Stream stream)
=> SaveAsTga(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the Tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTgaAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsTgaAsync(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance),
cancellationToken);
}
}

93
src/ImageSharp/Formats/Bmp/ImageExtensions.cs → src/ImageSharp/Formats/ImageExtensions.Save.tt

@ -1,10 +1,32 @@
<#@ template language="C#" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// <auto-generated />
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Bmp;
<#
var formats = new []{
"Bmp",
"Gif",
"Jpeg",
"Png",
"Tga",
};
foreach (string fmt in formats)
{
#>
using SixLabors.ImageSharp.Formats.<#= fmt #>;
<#
}
#>
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
@ -13,88 +35,115 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
public static partial class ImageExtensions public static partial class ImageExtensions
{ {
<#
foreach (string fmt in formats)
{
#>
/// <summary> /// <summary>
/// Saves the image to the given stream with the bmp format. /// Saves the image to the given stream with the <#= fmt #> format.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param> /// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsBmp(this Image source, string path) => SaveAsBmp(source, path, null); public static void SaveAs<#= fmt #>(this Image source, string path) => SaveAs<#= fmt #>(source, path, null);
/// <summary> /// <summary>
/// Saves the image to the given stream with the bmp format. /// Saves the image to the given stream with the <#= fmt #> format.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param> /// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsBmpAsync(this Image source, string path) => SaveAsBmpAsync(source, path, null); public static Task SaveAs<#= fmt #>Async(this Image source, string path) => SaveAs<#= fmt #>Async(source, path, null);
/// <summary> /// <summary>
/// Saves the image to the given stream with the bmp format. /// Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAs<#= fmt #>Async(this Image source, string path, CancellationToken cancellationToken)
=> SaveAs<#= fmt #>Async(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the <#= fmt #> format.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param> /// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param> /// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsBmp(this Image source, string path, BmpEncoder encoder) => public static void SaveAs<#= fmt #>(this Image source, string path, <#= fmt #>Encoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the bmp format. /// Saves the image to the given stream with the <#= fmt #> format.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param> /// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param> /// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder) => public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync( source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance),
cancellationToken);
/// <summary> /// <summary>
/// Saves the image to the given stream with the bmp format. /// Saves the image to the given stream with the <#= fmt #> format.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsBmp(this Image source, Stream stream) => SaveAsBmp(source, stream, null); public static void SaveAs<#= fmt #>(this Image source, Stream stream)
=> SaveAs<#= fmt #>(source, stream, null);
/// <summary> /// <summary>
/// Saves the image to the given stream with the bmp format. /// Saves the image to the given stream with the <#= fmt #> format.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsBmpAsync(this Image source, Stream stream) => SaveAsBmpAsync(source, stream, null); public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAs<#= fmt #>Async(source, stream, null, cancellationToken);
/// <summary> /// <summary>
/// Saves the image to the given stream with the bmp format. /// Saves the image to the given stream with the <#= fmt #> format.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param> /// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) => /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
source.Save( public static void SaveAs<#= fmt #>(this Image source, Stream stream, <#= fmt #>Encoder encoder)
=> source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the bmp format. /// Saves the image to the given stream with the <#= fmt #> format.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param> /// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder) => public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync( source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance),
cancellationToken);
<#
}
#>
} }
} }

17
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
@ -50,6 +51,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private HuffmanScanBuffer scanBuffer; private HuffmanScanBuffer scanBuffer;
private CancellationToken cancellationToken;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HuffmanScanDecoder"/> class. /// Initializes a new instance of the <see cref="HuffmanScanDecoder"/> class.
/// </summary> /// </summary>
@ -63,6 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <param name="spectralEnd">The spectral selection end.</param> /// <param name="spectralEnd">The spectral selection end.</param>
/// <param name="successiveHigh">The successive approximation bit high end.</param> /// <param name="successiveHigh">The successive approximation bit high end.</param>
/// <param name="successiveLow">The successive approximation bit low end.</param> /// <param name="successiveLow">The successive approximation bit low end.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
public HuffmanScanDecoder( public HuffmanScanDecoder(
BufferedReadStream stream, BufferedReadStream stream,
JpegFrame frame, JpegFrame frame,
@ -73,7 +77,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int spectralStart, int spectralStart,
int spectralEnd, int spectralEnd,
int successiveHigh, int successiveHigh,
int successiveLow) int successiveLow,
CancellationToken cancellationToken)
{ {
this.dctZigZag = ZigZag.CreateUnzigTable(); this.dctZigZag = ZigZag.CreateUnzigTable();
this.stream = stream; this.stream = stream;
@ -89,6 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.spectralEnd = spectralEnd; this.spectralEnd = spectralEnd;
this.successiveHigh = successiveHigh; this.successiveHigh = successiveHigh;
this.successiveLow = successiveLow; this.successiveLow = successiveLow;
this.cancellationToken = cancellationToken;
} }
/// <summary> /// <summary>
@ -96,6 +102,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
public void ParseEntropyCodedData() public void ParseEntropyCodedData()
{ {
this.cancellationToken.ThrowIfCancellationRequested();
if (!this.frame.Progressive) if (!this.frame.Progressive)
{ {
this.ParseBaselineData(); this.ParseBaselineData();
@ -145,6 +153,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int j = 0; j < mcusPerColumn; j++) for (int j = 0; j < mcusPerColumn; j++)
{ {
this.cancellationToken.ThrowIfCancellationRequested();
for (int i = 0; i < mcusPerLine; i++) for (int i = 0; i < mcusPerLine; i++)
{ {
// Scan an interleaved mcu... process components in order // Scan an interleaved mcu... process components in order
@ -210,6 +220,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int j = 0; j < h; j++) for (int j = 0; j < h; j++)
{ {
this.cancellationToken.ThrowIfCancellationRequested();
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(j); Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(j);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
@ -376,6 +387,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int j = 0; j < h; j++) for (int j = 0; j < h; j++)
{ {
this.cancellationToken.ThrowIfCancellationRequested();
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(j); Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(j);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
@ -402,6 +415,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int j = 0; j < h; j++) for (int j = 0; j < h; j++)
{ {
this.cancellationToken.ThrowIfCancellationRequested();
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(j); Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(j);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);

5
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Threading;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -111,7 +112,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam> /// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image</param> /// <param name="destination">The destination image</param>
public void PostProcess<TPixel>(ImageFrame<TPixel> destination) /// <param name="cancellationToken">The token to request cancellation.</param>
public void PostProcess<TPixel>(ImageFrame<TPixel> destination, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.PixelRowCounter = 0; this.PixelRowCounter = 0;
@ -123,6 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height) while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height)
{ {
cancellationToken.ThrowIfCancellationRequested();
this.DoPostProcessorStep(destination); this.DoPostProcessorStep(destination);
} }
} }

100
src/ImageSharp/Formats/Jpeg/ImageExtensions.cs

@ -1,100 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Jpeg;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Saves the image to the given stream with the jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsJpeg(this Image source, string path) => SaveAsJpeg(source, path, null);
/// <summary>
/// Saves the image to the given stream with the jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsJpegAsync(this Image source, string path) => SaveAsJpegAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsJpeg(this Image source, string path, JpegEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsJpeg(this Image source, Stream stream) => SaveAsJpeg(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsJpegAsync(this Image source, Stream stream) => SaveAsJpegAsync(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) =>
source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the jpeg format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance));
}
}

56
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -24,20 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this); using var decoder = new JpegDecoderCore(configuration, this);
try return decoder.Decode<TPixel>(configuration, stream);
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
(int w, int h) = (decoder.ImageWidth, decoder.ImageHeight);
JpegThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -45,31 +33,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
=> this.Decode<Rgba32>(configuration, stream); => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/> /// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream) public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this); using var decoder = new JpegDecoderCore(configuration, this);
try return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
(int w, int h) = (decoder.ImageWidth, decoder.ImageHeight);
JpegThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false); => await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/> /// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream) public IImageInfo Identify(Configuration configuration, Stream stream)
@ -77,20 +53,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this); using var decoder = new JpegDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream); return decoder.Identify(configuration, stream);
return decoder.Identify(bufferedStream);
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream) public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this); // The introduction of a local variable that refers to an object the implements
using var bufferedStream = new BufferedReadStream(configuration, stream); // IDisposable means you must use async/await, where the compiler generates the
// state machine and a continuation.
return decoder.IdentifyAsync(bufferedStream); using (var decoder = new JpegDecoderCore(configuration, this))
{
return await decoder.IdentifyAsync(configuration, stream, cancellationToken)
.ConfigureAwait(false);
}
} }
} }
} }

31
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
@ -117,6 +118,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/> /// <inheritdoc/>
public Size ImageSizeInPixels { get; private set; } public Size ImageSizeInPixels { get; private set; }
/// <inheritdoc/>
Size IImageDecoderInternals.Dimensions => this.ImageSizeInPixels;
/// <summary> /// <summary>
/// Gets the number of MCU blocks in the image as <see cref="Size"/>. /// Gets the number of MCU blocks in the image as <see cref="Size"/>.
/// </summary> /// </summary>
@ -205,21 +209,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.ParseStream(stream); this.ParseStream(stream, cancellationToken: cancellationToken);
this.InitExifProfile(); this.InitExifProfile();
this.InitIccProfile(); this.InitIccProfile();
this.InitIptcProfile(); this.InitIptcProfile();
this.InitDerivedMetadataProperties(); this.InitDerivedMetadataProperties();
return this.PostProcessIntoImage<TPixel>(); return this.PostProcessIntoImage<TPixel>(cancellationToken);
} }
/// <inheritdoc/> /// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream) public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{ {
this.ParseStream(stream, true); this.ParseStream(stream, true, cancellationToken);
this.InitExifProfile(); this.InitExifProfile();
this.InitIccProfile(); this.InitIccProfile();
this.InitIptcProfile(); this.InitIptcProfile();
@ -233,7 +237,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
/// <param name="stream">The input stream</param> /// <param name="stream">The input stream</param>
/// <param name="metadataOnly">Whether to decode metadata only.</param> /// <param name="metadataOnly">Whether to decode metadata only.</param>
public void ParseStream(BufferedReadStream stream, bool metadataOnly = false) /// <param name="cancellationToken">The token to monitor cancellation.</param>
public void ParseStream(BufferedReadStream stream, bool metadataOnly = false, CancellationToken cancellationToken = default)
{ {
this.Metadata = new ImageMetadata(); this.Metadata = new ImageMetadata();
@ -263,6 +268,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
while (fileMarker.Marker != JpegConstants.Markers.EOI while (fileMarker.Marker != JpegConstants.Markers.EOI
|| (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{ {
cancellationToken.ThrowIfCancellationRequested();
if (!fileMarker.Invalid) if (!fileMarker.Invalid)
{ {
// Get the marker length // Get the marker length
@ -279,7 +286,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.SOS: case JpegConstants.Markers.SOS:
if (!metadataOnly) if (!metadataOnly)
{ {
this.ProcessStartOfScanMarker(stream); this.ProcessStartOfScanMarker(stream, cancellationToken);
break; break;
} }
else else
@ -996,8 +1003,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// Processes the SOS (Start of scan marker). /// Processes the SOS (Start of scan marker).
/// </summary> /// </summary>
/// <param name="stream">The input stream.</param> private void ProcessStartOfScanMarker(BufferedReadStream stream, CancellationToken cancellationToken)
private void ProcessStartOfScanMarker(BufferedReadStream stream)
{ {
if (this.Frame is null) if (this.Frame is null)
{ {
@ -1048,7 +1054,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
spectralStart, spectralStart,
spectralEnd, spectralEnd,
successiveApproximation >> 4, successiveApproximation >> 4,
successiveApproximation & 15); successiveApproximation & 15,
cancellationToken);
sd.ParseEntropyCodedData(); sd.ParseEntropyCodedData();
} }
@ -1081,7 +1088,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
private Image<TPixel> PostProcessIntoImage<TPixel>() private Image<TPixel> PostProcessIntoImage<TPixel>(CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (this.ImageWidth == 0 || this.ImageHeight == 0) if (this.ImageWidth == 0 || this.ImageHeight == 0)
@ -1097,7 +1104,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
using (var postProcessor = new JpegImagePostProcessor(this.Configuration, this)) using (var postProcessor = new JpegImagePostProcessor(this.Configuration, this))
{ {
postProcessor.PostProcess(image.Frames.RootFrame); postProcessor.PostProcess(image.Frames.RootFrame, cancellationToken);
} }
return image; return image;

20
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -43,26 +44,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream) public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var encoder = new JpegEncoderCore(this); var encoder = new JpegEncoderCore(this);
return encoder.EncodeAsync(image, stream, cancellationToken);
if (stream.CanSeek)
{
encoder.Encode(image, stream);
}
else
{
// this hack has to be be here because JpegEncoderCore is unsafe
using (var ms = new MemoryStream())
{
encoder.Encode(image, ms);
ms.Position = 0;
await ms.CopyToAsync(stream).ConfigureAwait(false);
}
}
} }
} }
} }

26
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -6,6 +6,7 @@ using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// Image encoder for writing an image to a stream as a jpeg. /// Image encoder for writing an image to a stream as a jpeg.
/// </summary> /// </summary>
internal sealed unsafe class JpegEncoderCore internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals
{ {
/// <summary> /// <summary>
/// The number of quantization tables. /// The number of quantization tables.
@ -193,11 +194,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image to write from.</param> /// <param name="image">The image to write from.</param>
/// <param name="stream">The stream to write to.</param> /// <param name="stream">The stream to write to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream) /// <param name="cancellationToken">The token to request cancellation.</param>
where TPixel : unmanaged, IPixel<TPixel> public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
cancellationToken.ThrowIfCancellationRequested();
const ushort max = JpegConstants.MaxLength; const ushort max = JpegConstants.MaxLength;
if (image.Width >= max || image.Height >= max) if (image.Width >= max || image.Height >= max)
@ -246,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteDefineHuffmanTables(componentCount); this.WriteDefineHuffmanTables(componentCount);
// Write the image data. // Write the image data.
this.WriteStartOfScan(image); this.WriteStartOfScan(image, cancellationToken);
// Write the End Of Image marker. // Write the End Of Image marker.
this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
@ -395,7 +398,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param> /// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
private void Encode444<TPixel>(Image<TPixel> pixels) /// <param name="cancellationToken">The token to monitor for cancellation.</param>
private void Encode444<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -417,6 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int y = 0; y < pixels.Height; y += 8) for (int y = 0; y < pixels.Height; y += 8)
{ {
cancellationToken.ThrowIfCancellationRequested();
var currentRows = new RowOctet<TPixel>(pixelBuffer, y); var currentRows = new RowOctet<TPixel>(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8) for (int x = 0; x < pixels.Width; x += 8)
@ -941,7 +946,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The pixel accessor providing access to the image pixels.</param> /// <param name="image">The pixel accessor providing access to the image pixels.</param>
private void WriteStartOfScan<TPixel>(Image<TPixel> image) /// <param name="cancellationToken">The token to monitor for cancellation.</param>
private void WriteStartOfScan<TPixel>(Image<TPixel> image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -951,10 +957,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
switch (this.subsample) switch (this.subsample)
{ {
case JpegSubsample.Ratio444: case JpegSubsample.Ratio444:
this.Encode444(image); this.Encode444(image, cancellationToken);
break; break;
case JpegSubsample.Ratio420: case JpegSubsample.Ratio420:
this.Encode420(image); this.Encode420(image, cancellationToken);
break; break;
} }
@ -968,7 +974,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param> /// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
private void Encode420<TPixel>(Image<TPixel> pixels) /// <param name="cancellationToken">The token to monitor for cancellation.</param>
private void Encode420<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -993,6 +1000,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int y = 0; y < pixels.Height; y += 16) for (int y = 0; y < pixels.Height; y += 16)
{ {
cancellationToken.ThrowIfCancellationRequested();
for (int x = 0; x < pixels.Width; x += 16) for (int x = 0; x < pixels.Width; x += 16)
{ {
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)

100
src/ImageSharp/Formats/Png/ImageExtensions.cs

@ -1,100 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Saves the image to the given stream with the png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsPng(this Image source, string path) => SaveAsPng(source, path, null);
/// <summary>
/// Saves the image to the given stream with the png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPngAsync(this Image source, string path) => SaveAsPngAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsPng(this Image source, string path, PngEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsPng(this Image source, Stream stream) => SaveAsPng(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPngAsync(this Image source, Stream stream) => SaveAsPngAsync(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) =>
source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the png format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance));
}
}

49
src/ImageSharp/Formats/Png/PngDecoder.cs

@ -2,9 +2,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png namespace SixLabors.ImageSharp.Formats.Png
@ -22,65 +21,37 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new PngDecoderCore(configuration, this); var decoder = new PngDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
PngThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
} }
/// <inheritdoc /> /// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream); public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/> /// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream) public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new PngDecoderCore(configuration, this); var decoder = new PngDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
PngThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false); public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/> /// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream) public IImageInfo Identify(Configuration configuration, Stream stream)
{ {
var decoder = new PngDecoderCore(configuration, this); var decoder = new PngDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream); return decoder.Identify(configuration, stream);
return decoder.Identify(bufferedStream);
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream) public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{ {
var decoder = new PngDecoderCore(configuration, this); var decoder = new PngDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream); return decoder.IdentifyAsync(configuration, stream, cancellationToken);
return decoder.IdentifyAsync(bufferedStream);
} }
} }
} }

6
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -9,6 +9,8 @@ using System.IO.Compression;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Formats.Png.Zlib;
@ -131,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public Size Dimensions => new Size(this.header.Width, this.header.Height); public Size Dimensions => new Size(this.header.Width, this.header.Height);
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var metadata = new ImageMetadata(); var metadata = new ImageMetadata();
@ -223,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
/// <inheritdoc/> /// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream) public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{ {
var metadata = new ImageMetadata(); var metadata = new ImageMetadata();
PngMetadata pngMetadata = metadata.GetPngMetadata(); PngMetadata pngMetadata = metadata.GetPngMetadata();

9
src/ImageSharp/Formats/Png/PngEncoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -71,13 +72,17 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream) public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// The introduction of a local variable that refers to an object the implements
// IDisposable means you must use async/await, where the compiler generates the
// state machine and a continuation.
using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this)))
{ {
await encoder.EncodeAsync(image, stream).ConfigureAwait(false); await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false);
} }
} }
} }

30
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -7,6 +7,7 @@ using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Chunks;
@ -21,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Performs the png encoding operation. /// Performs the png encoding operation.
/// </summary> /// </summary>
internal sealed class PngEncoderCore : IDisposable internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
{ {
/// <summary> /// <summary>
/// The maximum block size, defaults at 64k for uncompressed blocks. /// The maximum block size, defaults at 64k for uncompressed blocks.
@ -127,31 +128,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream) /// <param name="cancellationToken">The token to request cancellation.</param>
where TPixel : unmanaged, IPixel<TPixel> public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
if (stream.CanSeek)
{
this.Encode(image, stream);
}
else
{
using (var ms = new MemoryStream())
{
this.Encode(image, ms);
ms.Position = 0;
await ms.CopyToAsync(stream).ConfigureAwait(false);
}
}
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));

100
src/ImageSharp/Formats/Tga/ImageExtensions.cs

@ -1,100 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Tga;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Saves the image to the given stream with the tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsTga(this Image source, string path) => SaveAsTga(source, path, null);
/// <summary>
/// Saves the image to the given stream with the tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTgaAsync(this Image source, string path) => SaveAsTgaAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsTga(this Image source, Stream stream) => SaveAsTga(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTgaAsync(this Image source, Stream stream) => SaveAsTgaAsync(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder) =>
source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the tga format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance));
}
}

48
src/ImageSharp/Formats/Tga/TgaDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -21,21 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this); var decoder = new TgaDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
TgaThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -43,49 +30,34 @@ namespace SixLabors.ImageSharp.Formats.Tga
=> this.Decode<Rgba32>(configuration, stream); => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/> /// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream) public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this); var decoder = new TgaDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
TgaThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false); => await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/> /// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream) public IImageInfo Identify(Configuration configuration, Stream stream)
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream); return new TgaDecoderCore(configuration, this).Identify(configuration, stream);
return new TgaDecoderCore(configuration, this).Identify(bufferedStream);
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream) public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream); return new TgaDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
return new TgaDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
} }
} }
} }

5
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height); public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height);
/// <inheritdoc /> /// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
try try
@ -640,7 +641,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
} }
/// <inheritdoc /> /// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream) public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{ {
this.ReadFileHeader(stream); this.ReadFileHeader(stream);
return new ImageInfo( return new ImageInfo(

5
src/ImageSharp/Formats/Tga/TgaEncoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -32,11 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream) public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream); return encoder.EncodeAsync(image, stream, cancellationToken);
} }
} }
} }

30
src/ImageSharp/Formats/Tga/TgaEncoderCore.cs

@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -16,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <summary> /// <summary>
/// Image encoder for writing an image to a stream as a truevision targa image. /// Image encoder for writing an image to a stream as a truevision targa image.
/// </summary> /// </summary>
internal sealed class TgaEncoderCore internal sealed class TgaEncoderCore : IImageEncoderInternals
{ {
/// <summary> /// <summary>
/// Used for allocating memory during processing operations. /// Used for allocating memory during processing operations.
@ -61,31 +62,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream) /// <param name="cancellationToken">The token to request cancellation.</param>
where TPixel : unmanaged, IPixel<TPixel> public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
if (stream.CanSeek)
{
this.Encode(image, stream);
}
else
{
using (var ms = new MemoryStream())
{
this.Encode(image, ms);
ms.Position = 0;
await ms.CopyToAsync(stream).ConfigureAwait(false);
}
}
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));

2
src/ImageSharp/IO/BufferedReadStream.cs

@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.IO
set set
{ {
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.Position)); Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.Position));
Guard.MustBeLessThan(value, this.Length, nameof(this.Position)); Guard.MustBeLessThanOrEqualTo(value, this.Length, nameof(this.Position));
// Only reset readBufferIndex if we are out of bounds of our working buffer // Only reset readBufferIndex if we are out of bounds of our working buffer
// otherwise we should simply move the value by the diff. // otherwise we should simply move the value by the diff.

22
src/ImageSharp/Image.Decode.cs

@ -4,6 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -156,18 +157,24 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="config">the configuration.</param> /// <param name="config">the configuration.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
private static async Task<(Image<TPixel> Image, IImageFormat Format)> DecodeAsync<TPixel>(Stream stream, Configuration config) private static async Task<(Image<TPixel> Image, IImageFormat Format)> DecodeAsync<TPixel>(
Stream stream,
Configuration config,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
(IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config)
.ConfigureAwait(false);
if (decoder is null) if (decoder is null)
{ {
return (null, null); return (null, null);
} }
Image<TPixel> img = await decoder.DecodeAsync<TPixel>(config, stream).ConfigureAwait(false); Image<TPixel> img = await decoder.DecodeAsync<TPixel>(config, stream, cancellationToken)
.ConfigureAwait(false);
return (img, format); return (img, format);
} }
@ -183,7 +190,7 @@ namespace SixLabors.ImageSharp
return (img, format); return (img, format);
} }
private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config) private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config, CancellationToken cancellationToken)
{ {
(IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false);
if (decoder is null) if (decoder is null)
@ -191,7 +198,7 @@ namespace SixLabors.ImageSharp
return (null, null); return (null, null);
} }
Image img = await decoder.DecodeAsync(config, stream).ConfigureAwait(false); Image img = await decoder.DecodeAsync(config, stream, cancellationToken).ConfigureAwait(false);
return (img, format); return (img, format);
} }
@ -221,11 +228,12 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="config">the configuration.</param> /// <param name="config">the configuration.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns> /// <returns>
/// A <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the /// A <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the
/// <see cref="IImageInfo"/> property of the returned type set to null if a suitable detector /// <see cref="IImageInfo"/> property of the returned type set to null if a suitable detector
/// is not found.</returns> /// is not found.</returns>
private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> InternalIdentityAsync(Stream stream, Configuration config) private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> InternalIdentityAsync(Stream stream, Configuration config, CancellationToken cancellationToken)
{ {
(IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false);
@ -239,7 +247,7 @@ namespace SixLabors.ImageSharp
return (null, format); return (null, format);
} }
IImageInfo info = await detector.IdentifyAsync(config, stream).ConfigureAwait(false); IImageInfo info = await detector.IdentifyAsync(config, stream, cancellationToken).ConfigureAwait(false);
return (info, format); return (info, format);
} }
} }

221
src/ImageSharp/Image.FromFile.cs

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -80,15 +81,75 @@ namespace SixLabors.ImageSharp
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <exception cref="NotSupportedException"> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// </exception> /// <returns>
/// <returns>The <see cref="Image"/>.</returns> /// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
public static Image Load(string path) /// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
=> Load(Configuration.Default, path); /// </returns>
public static Task<IImageInfo> IdentifyAsync(string filePath, CancellationToken cancellationToken = default)
=> IdentifyAsync(Configuration.Default, filePath, cancellationToken);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static async Task<IImageInfo> IdentifyAsync(
Configuration configuration,
string filePath,
CancellationToken cancellationToken = default)
{
(IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, filePath, cancellationToken)
.ConfigureAwait(false);
return res.ImageInfo;
}
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
string filePath,
CancellationToken cancellationToken = default)
=> IdentifyWithFormatAsync(Configuration.Default, filePath, cancellationToken);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
Configuration configuration,
string filePath,
CancellationToken cancellationToken = default)
{
Guard.NotNull(configuration, nameof(configuration));
using Stream stream = configuration.FileSystem.OpenRead(filePath);
return await IdentifyWithFormatAsync(configuration, stream, cancellationToken)
.ConfigureAwait(false);
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
@ -97,9 +158,9 @@ namespace SixLabors.ImageSharp
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Task<Image> LoadAsync(string path) public static Image Load(string path)
=> LoadAsync(Configuration.Default, path); => Load(Configuration.Default, path);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
@ -131,18 +192,21 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="configuration">The configuration for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image> LoadAsync(Configuration configuration, string path) public static async Task<Image> LoadAsync(
Configuration configuration,
string path,
CancellationToken cancellationToken = default)
{ {
using (Stream stream = configuration.FileSystem.OpenRead(path)) using Stream stream = configuration.FileSystem.OpenRead(path);
{ (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken)
(Image img, _) = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false); .ConfigureAwait(false);
return img; return img;
}
} }
/// <summary> /// <summary>
@ -168,27 +232,140 @@ namespace SixLabors.ImageSharp
} }
} }
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(string path, CancellationToken cancellationToken = default)
=> LoadAsync(Configuration.Default, path, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(string path, IImageDecoder decoder)
=> LoadAsync(Configuration.Default, path, decoder, default);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> LoadAsync<TPixel>(string path, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadAsync<TPixel>(Configuration.Default, path, decoder, default);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="configuration">The Configuration.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(
Configuration configuration,
string path,
IImageDecoder decoder,
CancellationToken cancellationToken = default)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using Stream stream = configuration.FileSystem.OpenRead(path);
return LoadAsync(configuration, stream, decoder, cancellationToken);
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary> /// </summary>
/// <param name="configuration">The Configuration.</param> /// <param name="configuration">The Configuration.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(Configuration configuration, string path, IImageDecoder decoder) public static Task<Image<TPixel>> LoadAsync<TPixel>(
Configuration configuration,
string path,
IImageDecoder decoder,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path)); Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path)) using Stream stream = configuration.FileSystem.OpenRead(path);
{ return LoadAsync<TPixel>(configuration, stream, decoder, cancellationToken);
return LoadAsync(configuration, stream, decoder); }
}
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> LoadAsync<TPixel>(string path)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadAsync<TPixel>(Configuration.Default, path, default(CancellationToken));
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image<TPixel>> LoadAsync<TPixel>(
Configuration configuration,
string path,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
{
using Stream stream = configuration.FileSystem.OpenRead(path);
(Image<TPixel> img, _) = await LoadWithFormatAsync<TPixel>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
return img;
} }
/// <summary> /// <summary>

103
src/ImageSharp/Image.FromStream.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -62,7 +63,8 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync( => WithSeekableStreamAsync(
configuration, configuration,
stream, stream,
s => InternalDetectFormatAsync(s, configuration)); (s, _) => InternalDetectFormatAsync(s, configuration),
default);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
@ -125,6 +127,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="stream">The image stream to read the information from.</param> /// <param name="stream">The image stream to read the information from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
@ -133,9 +136,12 @@ namespace SixLabors.ImageSharp
/// A <see cref="Task{IImageInfo}"/> representing the asynchronous operation or null if /// A <see cref="Task{IImageInfo}"/> representing the asynchronous operation or null if
/// a suitable detector is not found. /// a suitable detector is not found.
/// </returns> /// </returns>
public static async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream) public static async Task<IImageInfo> IdentifyAsync(
Configuration configuration,
Stream stream,
CancellationToken cancellationToken = default)
{ {
(IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream).ConfigureAwait(false); (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
return res.ImageInfo; return res.ImageInfo;
} }
@ -164,35 +170,43 @@ namespace SixLabors.ImageSharp
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </summary>
/// <param name="stream">The image stream to read the information from.</param> /// <param name="stream">The image stream to read the information from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns> /// <returns>
/// A <see cref="Task"/> representing the asynchronous operation or null if /// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
/// a suitable detector is not found. /// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(Stream stream) public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
=> IdentifyWithFormatAsync(Configuration.Default, stream); Stream stream,
CancellationToken cancellationToken = default)
=> IdentifyWithFormatAsync(Configuration.Default, stream, cancellationToken);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="stream">The image stream to read the information from.</param> /// <param name="stream">The image stream to read the information from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns> /// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asyncronous operation with the parameter type /// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found. /// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(Configuration configuration, Stream stream) public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
Configuration configuration,
Stream stream,
CancellationToken cancellationToken = default)
=> WithSeekableStreamAsync( => WithSeekableStreamAsync(
configuration, configuration,
stream, stream,
s => InternalIdentityAsync(s, configuration ?? Configuration.Default)); (s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct),
cancellationToken);
/// <summary> /// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream. /// Decode a new instance of the <see cref="Image"/> class from the given stream.
@ -302,6 +316,7 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
@ -309,13 +324,18 @@ namespace SixLabors.ImageSharp
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) public static Task<Image> LoadAsync(
Configuration configuration,
Stream stream,
IImageDecoder decoder,
CancellationToken cancellationToken = default)
{ {
Guard.NotNull(decoder, nameof(decoder)); Guard.NotNull(decoder, nameof(decoder));
return WithSeekableStreamAsync( return WithSeekableStreamAsync(
configuration, configuration,
stream, stream,
s => decoder.DecodeAsync(configuration, s)); (s, ct) => decoder.DecodeAsync(configuration, s, ct),
cancellationToken);
} }
/// <summary> /// <summary>
@ -336,15 +356,17 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="configuration">The configuration for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image> LoadAsync(Configuration configuration, Stream stream) public static async Task<Image> LoadAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken = default)
{ {
(Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false); (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream, cancellationToken)
.ConfigureAwait(false);
return fmt.Image; return fmt.Image;
} }
@ -425,18 +447,20 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> LoadAsync<TPixel>(Stream stream, IImageDecoder decoder) public static Task<Image<TPixel>> LoadAsync<TPixel>(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStreamAsync( => WithSeekableStreamAsync(
Configuration.Default, Configuration.Default,
stream, stream,
s => decoder.DecodeAsync<TPixel>(Configuration.Default, s)); (s, ct) => decoder.DecodeAsync<TPixel>(Configuration.Default, s, ct),
cancellationToken);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -461,6 +485,7 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The Configuration.</param> /// <param name="configuration">The Configuration.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
@ -468,12 +493,17 @@ namespace SixLabors.ImageSharp
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> LoadAsync<TPixel>(Configuration configuration, Stream stream, IImageDecoder decoder) public static Task<Image<TPixel>> LoadAsync<TPixel>(
Configuration configuration,
Stream stream,
IImageDecoder decoder,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStreamAsync( => WithSeekableStreamAsync(
configuration, configuration,
stream, stream,
s => decoder.DecodeAsync<TPixel>(configuration, s)); (s, ct) => decoder.DecodeAsync<TPixel>(configuration, s, ct),
cancellationToken);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -532,18 +562,23 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Configuration configuration, Stream stream) public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(
Configuration configuration,
Stream stream,
CancellationToken cancellationToken = default)
{ {
(Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync(
configuration, configuration,
stream, stream,
async s => await DecodeAsync(s, configuration).ConfigureAwait(false)) async (s, ct) => await DecodeAsync(s, configuration, ct).ConfigureAwait(false),
cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
if (data.Image != null) if (data.Image != null)
@ -567,6 +602,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
@ -574,14 +610,18 @@ namespace SixLabors.ImageSharp
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static async Task<(Image<TPixel> Image, IImageFormat Format)> LoadWithFormatAsync<TPixel>(Configuration configuration, Stream stream) public static async Task<(Image<TPixel> Image, IImageFormat Format)> LoadWithFormatAsync<TPixel>(
Configuration configuration,
Stream stream,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
(Image<TPixel> Image, IImageFormat Format) data = (Image<TPixel> Image, IImageFormat Format) data =
await WithSeekableStreamAsync( await WithSeekableStreamAsync(
configuration, configuration,
stream, stream,
s => DecodeAsync<TPixel>(s, configuration)) (s, ct) => DecodeAsync<TPixel>(s, configuration, ct),
cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
if (data.Image != null) if (data.Image != null)
@ -605,6 +645,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
@ -612,10 +653,14 @@ namespace SixLabors.ImageSharp
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task<Image<TPixel>> LoadAsync<TPixel>(Configuration configuration, Stream stream) public static async Task<Image<TPixel>> LoadAsync<TPixel>(
Configuration configuration,
Stream stream,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
(Image<TPixel> img, _) = await LoadWithFormatAsync<TPixel>(configuration, stream).ConfigureAwait(false); (Image<TPixel> img, _) = await LoadWithFormatAsync<TPixel>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
return img; return img;
} }
@ -700,11 +745,13 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="stream">The input stream.</param> /// <param name="stream">The input stream.</param>
/// <param name="action">The action to perform.</param> /// <param name="action">The action to perform.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The <see cref="Task{T}"/>.</returns> /// <returns>The <see cref="Task{T}"/>.</returns>
private static async Task<T> WithSeekableStreamAsync<T>( private static async Task<T> WithSeekableStreamAsync<T>(
Configuration configuration, Configuration configuration,
Stream stream, Stream stream,
Func<Stream, Task<T>> action) Func<Stream, CancellationToken, Task<T>> action,
CancellationToken cancellationToken)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -725,14 +772,14 @@ namespace SixLabors.ImageSharp
stream.Position = 0; stream.Position = 0;
} }
return await action(stream).ConfigureAwait(false); return await action(stream, cancellationToken).ConfigureAwait(false);
} }
using MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length); using MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length);
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize).ConfigureAwait(false); await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
memoryStream.Position = 0; memoryStream.Position = 0;
return await action(memoryStream).ConfigureAwait(false); return await action(memoryStream, cancellationToken).ConfigureAwait(false);
} }
} }
} }

13
src/ImageSharp/Image.cs

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
@ -103,15 +104,16 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param> /// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream or encoder is null.</exception> /// <exception cref="ArgumentNullException">Thrown if the stream or encoder is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public Task SaveAsync(Stream stream, IImageEncoder encoder) public Task SaveAsync(Stream stream, IImageEncoder encoder, CancellationToken cancellationToken = default)
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
Guard.NotNull(encoder, nameof(encoder)); Guard.NotNull(encoder, nameof(encoder));
this.EnsureNotDisposed(); this.EnsureNotDisposed();
return this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream)); return this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream), cancellationToken);
} }
/// <summary> /// <summary>
@ -162,7 +164,8 @@ namespace SixLabors.ImageSharp
/// with the pixel type of the image. /// with the pixel type of the image.
/// </summary> /// </summary>
/// <param name="visitor">The visitor.</param> /// <param name="visitor">The visitor.</param>
internal abstract Task AcceptAsync(IImageVisitorAsync visitor); /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
internal abstract Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken);
private class EncodeVisitor : IImageVisitor, IImageVisitorAsync private class EncodeVisitor : IImageVisitor, IImageVisitorAsync
{ {
@ -179,8 +182,8 @@ namespace SixLabors.ImageSharp
public void Visit<TPixel>(Image<TPixel> image) public void Visit<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> => this.encoder.Encode(image, this.stream); where TPixel : unmanaged, IPixel<TPixel> => this.encoder.Encode(image, this.stream);
public Task VisitAsync<TPixel>(Image<TPixel> image) public Task VisitAsync<TPixel>(Image<TPixel> image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> => this.encoder.EncodeAsync(image, this.stream); where TPixel : unmanaged, IPixel<TPixel> => this.encoder.EncodeAsync(image, this.stream, cancellationToken);
} }
} }
} }

19
src/ImageSharp/ImageExtensions.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
@ -30,10 +31,11 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param> /// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsync(this Image source, string path) public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default)
=> source.SaveAsync(path, source.DetectEncoder(path)); => source.SaveAsync(path, source.DetectEncoder(path), cancellationToken);
/// <summary> /// <summary>
/// Writes the image to the given stream using the currently loaded image format. /// Writes the image to the given stream using the currently loaded image format.
@ -59,17 +61,20 @@ namespace SixLabors.ImageSharp
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param> /// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param> /// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The encoder is null.</exception> /// <exception cref="ArgumentNullException">The encoder is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task SaveAsync(this Image source, string path, IImageEncoder encoder) public static async Task SaveAsync(
this Image source,
string path,
IImageEncoder encoder,
CancellationToken cancellationToken = default)
{ {
Guard.NotNull(path, nameof(path)); Guard.NotNull(path, nameof(path));
Guard.NotNull(encoder, nameof(encoder)); Guard.NotNull(encoder, nameof(encoder));
using (Stream fs = source.GetConfiguration().FileSystem.Create(path)) using Stream fs = source.GetConfiguration().FileSystem.Create(path);
{ await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false);
await source.SaveAsync(fs, encoder).ConfigureAwait(false);
}
} }
/// <summary> /// <summary>

15
src/ImageSharp/ImageSharp.csproj

@ -20,10 +20,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub"/> <PackageReference Include="Microsoft.SourceLink.GitHub" />
<PackageReference Include="MinVer" PrivateAssets="All" /> <PackageReference Include="MinVer" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" /> <PackageReference Include="System.Runtime.CompilerServices.Unsafe" />
</ItemGroup> </ItemGroup>
@ -41,7 +41,7 @@
<PackageReference Include="System.ValueTuple" /> <PackageReference Include="System.ValueTuple" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs"> <Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
@ -132,6 +132,11 @@
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DependentUpon>PorterDuffFunctions.Generated.tt</DependentUpon> <DependentUpon>PorterDuffFunctions.Generated.tt</DependentUpon>
</Compile> </Compile>
<Compile Update="Formats\ImageExtensions.Save.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ImageExtensions.Save.tt</DependentUpon>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -207,6 +212,10 @@
<LastGenOutput>DefaultPixelBlenders.Generated.cs</LastGenOutput> <LastGenOutput>DefaultPixelBlenders.Generated.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator> <Generator>TextTemplatingFileGenerator</Generator>
</None> </None>
<None Update="Formats\ImageExtensions.Save.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>ImageExtensions.Save.cs</LastGenOutput>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

5
src/ImageSharp/Image{TPixel}.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
@ -290,11 +291,11 @@ namespace SixLabors.ImageSharp
} }
/// <inheritdoc /> /// <inheritdoc />
internal override Task AcceptAsync(IImageVisitorAsync visitor) internal override Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken)
{ {
this.EnsureNotDisposed(); this.EnsureNotDisposed();
return visitor.VisitAsync(this); return visitor.VisitAsync(this, cancellationToken);
} }
/// <summary> /// <summary>

61
src/ImageSharp/Processing/EdgeDetectionOperators.cs

@ -1,61 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Enumerates the various types of defined edge detection filters.
/// </summary>
public enum EdgeDetectionOperators
{
/// <summary>
/// The Kayyali operator filter.
/// </summary>
Kayyali,
/// <summary>
/// The Kirsch operator filter.
/// </summary>
Kirsch,
/// <summary>
/// The Laplacian3X3 operator filter.
/// </summary>
Laplacian3x3,
/// <summary>
/// The Laplacian5X5 operator filter.
/// </summary>
Laplacian5x5,
/// <summary>
/// The LaplacianOfGaussian operator filter.
/// </summary>
LaplacianOfGaussian,
/// <summary>
/// The Prewitt operator filter.
/// </summary>
Prewitt,
/// <summary>
/// The RobertsCross operator filter.
/// </summary>
RobertsCross,
/// <summary>
/// The Robinson operator filter.
/// </summary>
Robinson,
/// <summary>
/// The Scharr operator filter.
/// </summary>
Scharr,
/// <summary>
/// The Sobel operator filter.
/// </summary>
Sobel
}
}

249
src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Processing.Processors.Convolution;
namespace SixLabors.ImageSharp.Processing namespace SixLabors.ImageSharp.Processing
@ -12,144 +11,230 @@ namespace SixLabors.ImageSharp.Processing
public static class DetectEdgesExtensions public static class DetectEdgesExtensions
{ {
/// <summary> /// <summary>
/// Detects any edges within the image. Uses the <see cref="SobelProcessor"/> filter /// Detects any edges within the image.
/// operating in grayscale mode. /// Uses the <see cref="KnownEdgeDetectorKernels.Sobel"/> kernel operating in grayscale mode.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) => public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) =>
DetectEdges(source, new SobelProcessor(true)); DetectEdges(source, KnownEdgeDetectorKernels.Sobel);
/// <summary> /// <summary>
/// Detects any edges within the image. Uses the <see cref="SobelProcessor"/> filter /// Detects any edges within the image.
/// operating in grayscale mode. /// Uses the <see cref="KnownEdgeDetectorKernels.Sobel"/> kernel operating in grayscale mode.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="rectangle"> /// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter. /// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param> /// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, Rectangle rectangle) => public static IImageProcessingContext DetectEdges(
DetectEdges(source, rectangle, new SobelProcessor(true)); this IImageProcessingContext source,
Rectangle rectangle) =>
DetectEdges(source, KnownEdgeDetectorKernels.Sobel, rectangle);
/// <summary> /// <summary>
/// Detects any edges within the image. /// Detects any edges within the image operating in grayscale mode.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="filter">The filter for detecting edges.</param> /// <param name="kernel">The 2D edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges( public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source, this IImageProcessingContext source,
EdgeDetectionOperators filter) => EdgeDetector2DKernel kernel) =>
DetectEdges(source, GetProcessor(filter, true)); DetectEdges(source, kernel, true);
/// <summary> /// <summary>
/// Detects any edges within the image. /// Detects any edges within the image using a <see cref="EdgeDetector2DKernel"/>.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="filter">The filter for detecting edges.</param> /// <param name="kernel">The 2D edge detector kernel.</param>
/// <param name="grayscale">Whether to convert the image to grayscale first. Defaults to true.</param> /// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges( public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source, this IImageProcessingContext source,
EdgeDetectionOperators filter, EdgeDetector2DKernel kernel,
bool grayscale) => bool grayscale)
DetectEdges(source, GetProcessor(filter, grayscale)); {
var processor = new EdgeDetector2DProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
/// <summary> /// <summary>
/// Detects any edges within the image. /// Detects any edges within the image operating in grayscale mode.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="filter">The filter for detecting edges.</param> /// <param name="kernel">The 2D edge detector kernel.</param>
/// <param name="rectangle"> /// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter. /// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param> /// </param>
/// <param name="grayscale">Whether to convert the image to grayscale first. Defaults to true.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges( public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source, this IImageProcessingContext source,
EdgeDetectionOperators filter, EdgeDetector2DKernel kernel,
Rectangle rectangle, Rectangle rectangle) =>
bool grayscale = true) => DetectEdges(source, kernel, true, rectangle);
DetectEdges(source, rectangle, GetProcessor(filter, grayscale));
/// <summary> /// <summary>
/// Detects any edges within the image. /// Detects any edges within the image using a <see cref="EdgeDetector2DKernel"/>.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="filter">The filter for detecting edges.</param> /// <param name="kernel">The 2D edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
private static IImageProcessingContext DetectEdges(this IImageProcessingContext source, IImageProcessor filter) public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetector2DKernel kernel,
bool grayscale,
Rectangle rectangle)
{ {
return source.ApplyProcessor(filter); var processor = new EdgeDetector2DProcessor(kernel, grayscale);
source.ApplyProcessor(processor, rectangle);
return source;
} }
/// <summary> /// <summary>
/// Detects any edges within the image. /// Detects any edges within the image operating in grayscale mode.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="rectangle"> /// <param name="kernel">The edge detector kernel.</param>
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter. /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorKernel kernel) =>
DetectEdges(source, kernel, true);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorKernel"/>.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="kernel">The edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param> /// </param>
/// <param name="filter">The filter for detecting edges.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
private static IImageProcessingContext DetectEdges( public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source, this IImageProcessingContext source,
Rectangle rectangle, EdgeDetectorKernel kernel,
IImageProcessor filter) bool grayscale)
{ {
source.ApplyProcessor(filter, rectangle); var processor = new EdgeDetectorProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source; return source;
} }
private static IImageProcessor GetProcessor(EdgeDetectionOperators filter, bool grayscale) /// <summary>
{ /// Detects any edges within the image operating in grayscale mode.
IImageProcessor processor; /// </summary>
/// <param name="source">The image this method extends.</param>
switch (filter) /// <param name="kernel">The edge detector kernel.</param>
{ /// <param name="rectangle">
case EdgeDetectionOperators.Kayyali: /// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
processor = new KayyaliProcessor(grayscale); /// </param>
break; /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
case EdgeDetectionOperators.Kirsch: this IImageProcessingContext source,
processor = new KirschProcessor(grayscale); EdgeDetectorKernel kernel,
break; Rectangle rectangle) =>
DetectEdges(source, kernel, true, rectangle);
case EdgeDetectionOperators.Laplacian3x3:
processor = new Laplacian3x3Processor(grayscale);
break;
case EdgeDetectionOperators.Laplacian5x5:
processor = new Laplacian5x5Processor(grayscale);
break;
case EdgeDetectionOperators.LaplacianOfGaussian:
processor = new LaplacianOfGaussianProcessor(grayscale);
break;
case EdgeDetectionOperators.Prewitt:
processor = new PrewittProcessor(grayscale);
break;
case EdgeDetectionOperators.RobertsCross: /// <summary>
processor = new RobertsCrossProcessor(grayscale); /// Detects any edges within the image using a <see cref="EdgeDetectorKernel"/>.
break; /// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="kernel">The edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorKernel kernel,
bool grayscale,
Rectangle rectangle)
{
var processor = new EdgeDetectorProcessor(kernel, grayscale);
source.ApplyProcessor(processor, rectangle);
return source;
}
case EdgeDetectionOperators.Robinson: /// <summary>
processor = new RobinsonProcessor(grayscale); /// Detects any edges within the image operating in grayscale mode.
break; /// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel) =>
DetectEdges(source, kernel, true);
case EdgeDetectionOperators.Scharr: /// <summary>
processor = new ScharrProcessor(grayscale); /// Detects any edges within the image using a <see cref="EdgeDetectorCompassKernel"/>.
break; /// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel,
bool grayscale)
{
var processor = new EdgeDetectorCompassProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
default: /// <summary>
processor = new SobelProcessor(grayscale); /// Detects any edges within the image operating in grayscale mode.
break; /// </summary>
} /// <param name="source">The image this method extends.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel,
Rectangle rectangle) =>
DetectEdges(source, kernel, true, rectangle);
return processor; /// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorCompassKernel"/>.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel,
bool grayscale,
Rectangle rectangle)
{
var processor = new EdgeDetectorCompassProcessor(kernel, grayscale);
source.ApplyProcessor(processor, rectangle);
return source;
} }
} }
} }

63
src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs

@ -0,0 +1,63 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Convolution;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Contains reusable static instances of known edge detection kernels.
/// </summary>
public static class KnownEdgeDetectorKernels
{
/// <summary>
/// Gets the Kayyali edge detector kernel.
/// </summary>
public static EdgeDetector2DKernel Kayyali { get; } = EdgeDetector2DKernel.KayyaliKernel;
/// <summary>
/// Gets the Kirsch edge detector kernel.
/// </summary>
public static EdgeDetectorCompassKernel Kirsch { get; } = EdgeDetectorCompassKernel.Kirsch;
/// <summary>
/// Gets the Laplacian 3x3 edge detector kernel.
/// </summary>
public static EdgeDetectorKernel Laplacian3x3 { get; } = EdgeDetectorKernel.Laplacian3x3;
/// <summary>
/// Gets the Laplacian 5x5 edge detector kernel.
/// </summary>
public static EdgeDetectorKernel Laplacian5x5 { get; } = EdgeDetectorKernel.Laplacian5x5;
/// <summary>
/// Gets the Laplacian of Gaussian edge detector kernel.
/// </summary>
public static EdgeDetectorKernel LaplacianOfGaussian { get; } = EdgeDetectorKernel.LaplacianOfGaussian;
/// <summary>
/// Gets the Prewitt edge detector kernel.
/// </summary>
public static EdgeDetector2DKernel Prewitt { get; } = EdgeDetector2DKernel.PrewittKernel;
/// <summary>
/// Gets the Roberts-Cross edge detector kernel.
/// </summary>
public static EdgeDetector2DKernel RobertsCross { get; } = EdgeDetector2DKernel.RobertsCrossKernel;
/// <summary>
/// Gets the Robinson edge detector kernel.
/// </summary>
public static EdgeDetectorCompassKernel Robinson { get; } = EdgeDetectorCompassKernel.Robinson;
/// <summary>
/// Gets the Scharr edge detector kernel.
/// </summary>
public static EdgeDetector2DKernel Scharr { get; } = EdgeDetector2DKernel.ScharrKernel;
/// <summary>
/// Gets the Sobel edge detector kernel.
/// </summary>
public static EdgeDetector2DKernel Sobel { get; } = EdgeDetector2DKernel.SobelKernel;
}
}

42
src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using the two 1D gradient operators.
/// </summary>
public sealed class EdgeDetector2DProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetector2DProcessor"/> class.
/// </summary>
/// <param name="kernel">The 2D edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
public EdgeDetector2DProcessor(EdgeDetector2DKernel kernel, bool grayscale)
{
this.Kernel = kernel;
this.Grayscale = grayscale;
}
/// <summary>
/// Gets the 2D edge detector kernel.
/// </summary>
public EdgeDetector2DKernel Kernel { get; }
/// <summary>
/// Gets a value indicating whether to convert the image to grayscale before performing
/// edge detection.
/// </summary>
public bool Grayscale { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new EdgeDetector2DProcessor<TPixel>(configuration, this, source, sourceRectangle);
}
}

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

@ -13,42 +13,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
internal class EdgeDetector2DProcessor<TPixel> : ImageProcessor<TPixel> internal class EdgeDetector2DProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly DenseMatrix<float> kernelX;
private readonly DenseMatrix<float> kernelY;
private readonly bool grayscale;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EdgeDetector2DProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="EdgeDetector2DProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="kernelX">The horizontal gradient operator.</param> /// <param name="definition">The <see cref="EdgeDetector2DProcessor"/> defining the processor parameters.</param>
/// <param name="kernelY">The vertical gradient operator.</param>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param> /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param> /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
internal EdgeDetector2DProcessor( public EdgeDetector2DProcessor(
Configuration configuration, Configuration configuration,
in DenseMatrix<float> kernelX, EdgeDetector2DProcessor definition,
in DenseMatrix<float> kernelY,
bool grayscale,
Image<TPixel> source, Image<TPixel> source,
Rectangle sourceRectangle) Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); this.kernelX = definition.Kernel.KernelX;
this.KernelX = kernelX; this.kernelY = definition.Kernel.KernelY;
this.KernelY = kernelY; this.grayscale = definition.Grayscale;
this.Grayscale = grayscale;
} }
/// <summary>
/// Gets the horizontal gradient operator.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public DenseMatrix<float> KernelY { get; }
public bool Grayscale { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeImageApply() protected override void BeforeImageApply()
{ {
@ -57,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
opaque.Execute(); opaque.Execute();
} }
if (this.Grayscale) if (this.grayscale)
{ {
new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle);
} }
@ -68,7 +55,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc /> /// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using var processor = new Convolution2DProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle); using var processor = new Convolution2DProcessor<TPixel>(
this.Configuration,
in this.kernelX,
in this.kernelY,
true,
this.Source,
this.SourceRectangle);
processor.Apply(source); processor.Apply(source);
} }

42
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using eight gradient operators.
/// </summary>
public sealed class EdgeDetectorCompassProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorCompassProcessor"/> class.
/// </summary>
/// <param name="kernel">The edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
public EdgeDetectorCompassProcessor(EdgeDetectorCompassKernel kernel, bool grayscale)
{
this.Kernel = kernel;
this.Grayscale = grayscale;
}
/// <summary>
/// Gets the edge detector kernel.
/// </summary>
public EdgeDetectorCompassKernel Kernel { get; }
/// <summary>
/// Gets a value indicating whether to convert the image to grayscale before performing
/// edge detection.
/// </summary>
public bool Grayscale { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new EdgeDetectorCompassProcessor<TPixel>(configuration, this, source, sourceRectangle);
}
}

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

@ -18,25 +18,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
internal class EdgeDetectorCompassProcessor<TPixel> : ImageProcessor<TPixel> internal class EdgeDetectorCompassProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly DenseMatrix<float>[] kernels;
private readonly bool grayscale;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorCompassProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="EdgeDetectorCompassProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="kernels">Gets the kernels to use.</param> /// <param name="definition">The <see cref="EdgeDetectorCompassProcessor"/> defining the processor parameters.</param>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param> /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param> /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
internal EdgeDetectorCompassProcessor(Configuration configuration, CompassKernels kernels, bool grayscale, Image<TPixel> source, Rectangle sourceRectangle) internal EdgeDetectorCompassProcessor(
Configuration configuration,
EdgeDetectorCompassProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
this.Grayscale = grayscale; this.grayscale = definition.Grayscale;
this.Kernels = kernels; this.kernels = definition.Kernel.Flatten();
} }
private CompassKernels Kernels { get; }
private bool Grayscale { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeImageApply() protected override void BeforeImageApply()
{ {
@ -45,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
opaque.Execute(); opaque.Execute();
} }
if (this.Grayscale) if (this.grayscale)
{ {
new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle);
} }
@ -56,29 +58,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc /> /// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
DenseMatrix<float>[] kernels = this.Kernels.Flatten();
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
// 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();
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, kernels[0], true, this.Source, interest)) using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernels[0], true, this.Source, interest))
{ {
processor.Apply(source); processor.Apply(source);
} }
if (kernels.Length == 1) if (this.kernels.Length == 1)
{ {
return; return;
} }
// Additional runs // Additional runs
for (int i = 1; i < kernels.Length; i++) for (int i = 1; i < this.kernels.Length; i++)
{ {
using ImageFrame<TPixel> pass = cleanCopy.Clone(); using ImageFrame<TPixel> pass = cleanCopy.Clone();
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, kernels[i], true, this.Source, interest)) using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernels[i], true, this.Source, interest))
{ {
processor.Apply(pass); processor.Apply(pass);
} }

25
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs

@ -6,26 +6,37 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
/// Defines a processor that detects edges within an image using a single two dimensional matrix. /// Defines edge detection using a single 2D gradient operator.
/// </summary> /// </summary>
public abstract class EdgeDetectorProcessor : IImageProcessor public sealed class EdgeDetectorProcessor : IImageProcessor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorProcessor"/> class. /// Initializes a new instance of the <see cref="EdgeDetectorProcessor"/> class.
/// </summary> /// </summary>
/// <param name="grayscale">A value indicating whether to convert the image to grayscale before performing edge detection.</param> /// <param name="kernel">The edge detector kernel.</param>
protected EdgeDetectorProcessor(bool grayscale) /// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
public EdgeDetectorProcessor(EdgeDetectorKernel kernel, bool grayscale)
{ {
this.Kernel = kernel;
this.Grayscale = grayscale; this.Grayscale = grayscale;
} }
/// <summary> /// <summary>
/// Gets a value indicating whether to convert the image to grayscale before performing edge detection. /// Gets the edge detector kernel.
/// </summary>
public EdgeDetectorKernel Kernel { get; }
/// <summary>
/// Gets a value indicating whether to convert the image to grayscale before performing
/// edge detection.
/// </summary> /// </summary>
public bool Grayscale { get; } public bool Grayscale { get; }
/// <inheritdoc /> /// <inheritdoc />
public abstract IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>
=> new EdgeDetectorProcessor<TPixel>(configuration, this, source, sourceRectangle);
} }
} }

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

@ -13,33 +13,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
internal class EdgeDetectorProcessor<TPixel> : ImageProcessor<TPixel> internal class EdgeDetectorProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly bool grayscale;
private readonly DenseMatrix<float> kernelXY;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="EdgeDetectorProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="kernelXY">The 2d gradient operator.</param> /// <param name="definition">The <see cref="EdgeDetectorProcessor"/> defining the processor parameters.</param>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param> /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The target area to process for the current processor instance.</param> /// <param name="sourceRectangle">The target area to process for the current processor instance.</param>
public EdgeDetectorProcessor( public EdgeDetectorProcessor(
Configuration configuration, Configuration configuration,
in DenseMatrix<float> kernelXY, EdgeDetectorProcessor definition,
bool grayscale,
Image<TPixel> source, Image<TPixel> source,
Rectangle sourceRectangle) Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
this.KernelXY = kernelXY; this.kernelXY = definition.Kernel.KernelXY;
this.Grayscale = grayscale; this.grayscale = definition.Grayscale;
} }
public bool Grayscale { get; }
/// <summary>
/// Gets the 2d gradient operator.
/// </summary>
public DenseMatrix<float> KernelXY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeImageApply() protected override void BeforeImageApply()
{ {
@ -48,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
opaque.Execute(); opaque.Execute();
} }
if (this.Grayscale) if (this.grayscale)
{ {
new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle);
} }
@ -59,10 +53,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, this.KernelXY, true, this.Source, this.SourceRectangle)) using var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle);
{ processor.Apply(source);
processor.Apply(source);
}
} }
} }
} }

31
src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs

@ -1,31 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection processing using the Kayyali operator filter.
/// See <see href="http://edgedetection.webs.com/"/>.
/// </summary>
public sealed class KayyaliProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="KayyaliProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public KayyaliProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetector2DProcessor<TPixel>(
configuration,
KayyaliKernels.KayyaliX,
KayyaliKernels.KayyaliY,
this.Grayscale,
source,
sourceRectangle);
}
}

55
src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs

@ -1,55 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
internal abstract class CompassKernels
{
/// <summary>
/// Gets the North gradient operator.
/// </summary>
public abstract DenseMatrix<float> North { get; }
/// <summary>
/// Gets the NorthWest gradient operator.
/// </summary>
public abstract DenseMatrix<float> NorthWest { get; }
/// <summary>
/// Gets the West gradient operator.
/// </summary>
public abstract DenseMatrix<float> West { get; }
/// <summary>
/// Gets the SouthWest gradient operator.
/// </summary>
public abstract DenseMatrix<float> SouthWest { get; }
/// <summary>
/// Gets the South gradient operator.
/// </summary>
public abstract DenseMatrix<float> South { get; }
/// <summary>
/// Gets the SouthEast gradient operator.
/// </summary>
public abstract DenseMatrix<float> SouthEast { get; }
/// <summary>
/// Gets the East gradient operator.
/// </summary>
public abstract DenseMatrix<float> East { get; }
/// <summary>
/// Gets the NorthEast gradient operator.
/// </summary>
public abstract DenseMatrix<float> NorthEast { get; }
public DenseMatrix<float>[] Flatten() =>
new[]
{
this.North, this.NorthWest, this.West, this.SouthWest,
this.South, this.SouthEast, this.East, this.NorthEast
};
}
}

103
src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs

@ -0,0 +1,103 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Represents an edge detection convolution kernel consisting of two 1D gradient operators.
/// </summary>
public readonly struct EdgeDetector2DKernel : IEquatable<EdgeDetector2DKernel>
{
/// <summary>
/// An edge detection kernel containing two Kayyali operators.
/// </summary>
public static EdgeDetector2DKernel KayyaliKernel = new EdgeDetector2DKernel(KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliY);
/// <summary>
/// An edge detection kernel containing two Prewitt operators.
/// <see href="https://en.wikipedia.org/wiki/Prewitt_operator"/>.
/// </summary>
public static EdgeDetector2DKernel PrewittKernel = new EdgeDetector2DKernel(PrewittKernels.PrewittX, PrewittKernels.PrewittY);
/// <summary>
/// An edge detection kernel containing two Roberts-Cross operators.
/// <see href="https://en.wikipedia.org/wiki/Roberts_cross"/>.
/// </summary>
public static EdgeDetector2DKernel RobertsCrossKernel = new EdgeDetector2DKernel(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY);
/// <summary>
/// An edge detection kernel containing two Scharr operators.
/// </summary>
public static EdgeDetector2DKernel ScharrKernel = new EdgeDetector2DKernel(ScharrKernels.ScharrX, ScharrKernels.ScharrY);
/// <summary>
/// An edge detection kernel containing two Sobel operators.
/// <see href="https://en.wikipedia.org/wiki/Sobel_operator"/>.
/// </summary>
public static EdgeDetector2DKernel SobelKernel = new EdgeDetector2DKernel(SobelKernels.SobelX, SobelKernels.SobelY);
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetector2DKernel"/> struct.
/// </summary>
/// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param>
public EdgeDetector2DKernel(DenseMatrix<float> kernelX, DenseMatrix<float> kernelY)
{
Guard.IsTrue(
kernelX.Size.Equals(kernelY.Size),
$"{nameof(kernelX)} {nameof(kernelY)}",
"Kernel sizes must be the same.");
this.KernelX = kernelX;
this.KernelY = kernelY;
}
/// <summary>
/// Gets the horizontal gradient operator.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public DenseMatrix<float> KernelY { get; }
/// <summary>
/// Checks whether two <see cref="EdgeDetector2DKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetector2DKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetector2DKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator ==(EdgeDetector2DKernel left, EdgeDetector2DKernel right)
=> left.Equals(right);
/// <summary>
/// Checks whether two <see cref="EdgeDetector2DKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetector2DKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetector2DKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator !=(EdgeDetector2DKernel left, EdgeDetector2DKernel right)
=> !(left == right);
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is EdgeDetector2DKernel kernel && this.Equals(kernel);
/// <inheritdoc/>
public bool Equals(EdgeDetector2DKernel other)
=> this.KernelX.Equals(other.KernelX)
&& this.KernelY.Equals(other.KernelY);
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(this.KernelX, this.KernelY);
}
}

163
src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs

@ -0,0 +1,163 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Represents an edge detection convolution kernel consisting of eight gradient operators.
/// </summary>
public readonly struct EdgeDetectorCompassKernel : IEquatable<EdgeDetectorCompassKernel>
{
/// <summary>
/// An edge detection kenel comprised of Kirsch gradient operators.
/// <see href="http://en.wikipedia.org/wiki/Kirsch_operator"/>.
/// </summary>
public static EdgeDetectorCompassKernel Kirsch =
new EdgeDetectorCompassKernel(
KirschKernels.North,
KirschKernels.NorthWest,
KirschKernels.West,
KirschKernels.SouthWest,
KirschKernels.South,
KirschKernels.SouthEast,
KirschKernels.East,
KirschKernels.NorthEast);
/// <summary>
/// An edge detection kenel comprised of Robinson gradient operators.
/// <see href="http://www.tutorialspoint.com/dip/Robinson_Compass_Mask.htm"/>
/// </summary>
public static EdgeDetectorCompassKernel Robinson =
new EdgeDetectorCompassKernel(
RobinsonKernels.North,
RobinsonKernels.NorthWest,
RobinsonKernels.West,
RobinsonKernels.SouthWest,
RobinsonKernels.South,
RobinsonKernels.SouthEast,
RobinsonKernels.East,
RobinsonKernels.NorthEast);
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorCompassKernel"/> struct.
/// </summary>
/// <param name="north">The north gradient operator.</param>
/// <param name="northWest">The north-west gradient operator.</param>
/// <param name="west">The west gradient operator.</param>
/// <param name="southWest">The south-west gradient operator.</param>
/// <param name="south">The south gradient operator.</param>
/// <param name="southEast">The south-east gradient operator.</param>
/// <param name="east">The east gradient operator.</param>
/// <param name="northEast">The north-east gradient operator.</param>
public EdgeDetectorCompassKernel(
DenseMatrix<float> north,
DenseMatrix<float> northWest,
DenseMatrix<float> west,
DenseMatrix<float> southWest,
DenseMatrix<float> south,
DenseMatrix<float> southEast,
DenseMatrix<float> east,
DenseMatrix<float> northEast)
{
this.North = north;
this.NorthWest = northWest;
this.West = west;
this.SouthWest = southWest;
this.South = south;
this.SouthEast = southEast;
this.East = east;
this.NorthEast = northEast;
}
/// <summary>
/// Gets the North gradient operator.
/// </summary>
public DenseMatrix<float> North { get; }
/// <summary>
/// Gets the NorthWest gradient operator.
/// </summary>
public DenseMatrix<float> NorthWest { get; }
/// <summary>
/// Gets the West gradient operator.
/// </summary>
public DenseMatrix<float> West { get; }
/// <summary>
/// Gets the SouthWest gradient operator.
/// </summary>
public DenseMatrix<float> SouthWest { get; }
/// <summary>
/// Gets the South gradient operator.
/// </summary>
public DenseMatrix<float> South { get; }
/// <summary>
/// Gets the SouthEast gradient operator.
/// </summary>
public DenseMatrix<float> SouthEast { get; }
/// <summary>
/// Gets the East gradient operator.
/// </summary>
public DenseMatrix<float> East { get; }
/// <summary>
/// Gets the NorthEast gradient operator.
/// </summary>
public DenseMatrix<float> NorthEast { get; }
/// <summary>
/// Checks whether two <see cref="EdgeDetectorCompassKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetectorCompassKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetectorCompassKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator ==(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right)
=> left.Equals(right);
/// <summary>
/// Checks whether two <see cref="EdgeDetectorCompassKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetectorCompassKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetectorCompassKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator !=(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right)
=> !(left == right);
/// <inheritdoc/>
public override bool Equals(object obj) => obj is EdgeDetectorCompassKernel kernel && this.Equals(kernel);
/// <inheritdoc/>
public bool Equals(EdgeDetectorCompassKernel other) => this.North.Equals(other.North) && this.NorthWest.Equals(other.NorthWest) && this.West.Equals(other.West) && this.SouthWest.Equals(other.SouthWest) && this.South.Equals(other.South) && this.SouthEast.Equals(other.SouthEast) && this.East.Equals(other.East) && this.NorthEast.Equals(other.NorthEast);
/// <inheritdoc/>
public override int GetHashCode()
=> HashCode.Combine(
this.North,
this.NorthWest,
this.West,
this.SouthWest,
this.South,
this.SouthEast,
this.East,
this.NorthEast);
internal DenseMatrix<float>[] Flatten() =>
new[]
{
this.North, this.NorthWest, this.West, this.SouthWest,
this.South, this.SouthEast, this.East, this.NorthEast
};
}
}

78
src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs

@ -0,0 +1,78 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Represents an edge detection convolution kernel consisting of a single 2D gradient operator.
/// </summary>
public readonly struct EdgeDetectorKernel : IEquatable<EdgeDetectorKernel>
{
/// <summary>
/// An edge detection kernel containing a 3x3 Laplacian operator.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>
/// </summary>
public static EdgeDetectorKernel Laplacian3x3 = new EdgeDetectorKernel(LaplacianKernels.Laplacian3x3);
/// <summary>
/// An edge detection kernel containing a 5x5 Laplacian operator.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>
/// </summary>
public static EdgeDetectorKernel Laplacian5x5 = new EdgeDetectorKernel(LaplacianKernels.Laplacian5x5);
/// <summary>
/// An edge detection kernel containing a Laplacian of Gaussian operator.
/// <see href="http://fourier.eng.hmc.edu/e161/lectures/gradient/node8.html"/>.
/// </summary>
public static EdgeDetectorKernel LaplacianOfGaussian = new EdgeDetectorKernel(LaplacianKernels.LaplacianOfGaussianXY);
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorKernel"/> struct.
/// </summary>
/// <param name="kernelXY">The 2D gradient operator.</param>
public EdgeDetectorKernel(DenseMatrix<float> kernelXY)
=> this.KernelXY = kernelXY;
/// <summary>
/// Gets the 2D gradient operator.
/// </summary>
public DenseMatrix<float> KernelXY { get; }
/// <summary>
/// Checks whether two <see cref="EdgeDetectorKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetectorKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetectorKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator ==(EdgeDetectorKernel left, EdgeDetectorKernel right)
=> left.Equals(right);
/// <summary>
/// Checks whether two <see cref="EdgeDetectorKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetectorKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetectorKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator !=(EdgeDetectorKernel left, EdgeDetectorKernel right)
=> !(left == right);
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is EdgeDetectorKernel kernel && this.Equals(kernel);
/// <inheritdoc/>
public bool Equals(EdgeDetectorKernel other)
=> this.KernelXY.Equals(other.KernelXY);
/// <inheritdoc/>
public override int GetHashCode() => this.KernelXY.GetHashCode();
}
}

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs

25
src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs

@ -1,17 +1,18 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
/// Contains the eight matrices used for Kirsch edge detection /// Contains the eight matrices used for Kirsch edge detection.
/// <see href="http://en.wikipedia.org/wiki/Kirsch_operator"/>.
/// </summary> /// </summary>
internal class KirschKernels : CompassKernels internal static class KirschKernels
{ {
/// <summary> /// <summary>
/// Gets the North gradient operator /// Gets the North gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> North => public static DenseMatrix<float> North =>
new float[,] new float[,]
{ {
{ 5, 5, 5 }, { 5, 5, 5 },
@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the NorthWest gradient operator /// Gets the NorthWest gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> NorthWest => public static DenseMatrix<float> NorthWest =>
new float[,] new float[,]
{ {
{ 5, 5, -3 }, { 5, 5, -3 },
@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the West gradient operator /// Gets the West gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> West => public static DenseMatrix<float> West =>
new float[,] new float[,]
{ {
{ 5, -3, -3 }, { 5, -3, -3 },
@ -44,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the SouthWest gradient operator /// Gets the SouthWest gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> SouthWest => public static DenseMatrix<float> SouthWest =>
new float[,] new float[,]
{ {
{ -3, -3, -3 }, { -3, -3, -3 },
@ -55,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the South gradient operator /// Gets the South gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> South => public static DenseMatrix<float> South =>
new float[,] new float[,]
{ {
{ -3, -3, -3 }, { -3, -3, -3 },
@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the SouthEast gradient operator /// Gets the SouthEast gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> SouthEast => public static DenseMatrix<float> SouthEast =>
new float[,] new float[,]
{ {
{ -3, -3, -3 }, { -3, -3, -3 },
@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the East gradient operator /// Gets the East gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> East => public static DenseMatrix<float> East =>
new float[,] new float[,]
{ {
{ -3, -3, 5 }, { -3, -3, 5 },
@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the NorthEast gradient operator /// Gets the NorthEast gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> NorthEast => public static DenseMatrix<float> NorthEast =>
new float[,] new float[,]
{ {
{ -3, 5, 5 }, { -3, 5, 5 },
@ -96,4 +97,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ -3, -3, -3 } { -3, -3, -3 }
}; };
} }
} }

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs

8
src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs

@ -1,10 +1,12 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
/// Contains Laplacian kernels of different sizes /// Contains Laplacian kernels of different sizes.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>
/// <see href="http://fourier.eng.hmc.edu/e161/lectures/gradient/node8.html"/>.
/// </summary> /// </summary>
internal static class LaplacianKernels internal static class LaplacianKernels
{ {
@ -31,4 +33,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ 0, 0, -1, 0, 0 } { 0, 0, -1, 0, 0 }
}; };
} }
} }

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs

23
src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs

@ -1,17 +1,18 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
/// Contains the kernels used for Robinson edge detection. /// Contains the kernels used for Robinson edge detection.
/// <see href="http://www.tutorialspoint.com/dip/Robinson_Compass_Mask.htm"/>
/// </summary> /// </summary>
internal class RobinsonKernels : CompassKernels internal static class RobinsonKernels
{ {
/// <summary> /// <summary>
/// Gets the North gradient operator /// Gets the North gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> North => public static DenseMatrix<float> North =>
new float[,] new float[,]
{ {
{ 1, 2, 1 }, { 1, 2, 1 },
@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the NorthWest gradient operator /// Gets the NorthWest gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> NorthWest => public static DenseMatrix<float> NorthWest =>
new float[,] new float[,]
{ {
{ 2, 1, 0 }, { 2, 1, 0 },
@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the West gradient operator /// Gets the West gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> West => public static DenseMatrix<float> West =>
new float[,] new float[,]
{ {
{ 1, 0, -1 }, { 1, 0, -1 },
@ -44,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the SouthWest gradient operator /// Gets the SouthWest gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> SouthWest => public static DenseMatrix<float> SouthWest =>
new float[,] new float[,]
{ {
{ 0, -1, -2 }, { 0, -1, -2 },
@ -55,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the South gradient operator /// Gets the South gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> South => public static DenseMatrix<float> South =>
new float[,] new float[,]
{ {
{ -1, -2, -1 }, { -1, -2, -1 },
@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the SouthEast gradient operator /// Gets the SouthEast gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> SouthEast => public static DenseMatrix<float> SouthEast =>
new float[,] new float[,]
{ {
{ -2, -1, 0 }, { -2, -1, 0 },
@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the East gradient operator /// Gets the East gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> East => public static DenseMatrix<float> East =>
new float[,] new float[,]
{ {
{ -1, 0, 1 }, { -1, 0, 1 },
@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Gets the NorthEast gradient operator /// Gets the NorthEast gradient operator
/// </summary> /// </summary>
public override DenseMatrix<float> NorthEast => public static DenseMatrix<float> NorthEast =>
new float[,] new float[,]
{ {
{ 0, 1, 2 }, { 0, 1, 2 },
@ -96,4 +97,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ -2, -1, 0 } { -2, -1, 0 }
}; };
} }
} }

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs

25
src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using the Kirsch operator filter.
/// See <see href="http://en.wikipedia.org/wiki/Kirsch_operator"/>.
/// </summary>
public sealed class KirschProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="KirschProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public KirschProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetectorCompassProcessor<TPixel>(configuration, new KirschKernels(), this.Grayscale, source, sourceRectangle);
}
}

25
src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Applies edge detection processing to the image using the Laplacian 3x3 operator filter.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>
/// </summary>
public sealed class Laplacian3x3Processor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="Laplacian3x3Processor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public Laplacian3x3Processor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetectorProcessor<TPixel>(configuration, LaplacianKernels.Laplacian3x3, this.Grayscale, source, sourceRectangle);
}
}

25
src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection processing using the Laplacian 5x5 operator filter.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>.
/// </summary>
public sealed class Laplacian5x5Processor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="Laplacian5x5Processor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public Laplacian5x5Processor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetectorProcessor<TPixel>(configuration, LaplacianKernels.Laplacian5x5, this.Grayscale, source, sourceRectangle);
}
}

25
src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Applies edge detection processing to the image using the Laplacian of Gaussian operator filter.
/// See <see href="http://fourier.eng.hmc.edu/e161/lectures/gradient/node8.html"/>.
/// </summary>
public sealed class LaplacianOfGaussianProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="LaplacianOfGaussianProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public LaplacianOfGaussianProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetectorProcessor<TPixel>(configuration, LaplacianKernels.LaplacianOfGaussianXY, this.Grayscale, source, sourceRectangle);
}
}

31
src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs

@ -1,31 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using the Prewitt operator filter.
/// See <see href="http://en.wikipedia.org/wiki/Prewitt_operator"/>.
/// </summary>
public sealed class PrewittProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="PrewittProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public PrewittProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetector2DProcessor<TPixel>(
configuration,
PrewittKernels.PrewittX,
PrewittKernels.PrewittY,
this.Grayscale,
source,
sourceRectangle);
}
}

31
src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs

@ -1,31 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection processing using the Roberts Cross operator filter.
/// See <see href="http://en.wikipedia.org/wiki/Roberts_cross"/>.
/// </summary>
public sealed class RobertsCrossProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="RobertsCrossProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public RobertsCrossProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetector2DProcessor<TPixel>(
configuration,
RobertsCrossKernels.RobertsCrossX,
RobertsCrossKernels.RobertsCrossY,
this.Grayscale,
source,
sourceRectangle);
}
}

25
src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using the Robinson operator filter.
/// See <see href="http://www.tutorialspoint.com/dip/Robinson_Compass_Mask.htm"/>.
/// </summary>
public sealed class RobinsonProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="RobinsonProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public RobinsonProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetectorCompassProcessor<TPixel>(configuration, new RobinsonKernels(), this.Grayscale, source, sourceRectangle);
}
}

31
src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs

@ -1,31 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection processing using the Scharr operator filter.
/// <see href="http://en.wikipedia.org/wiki/Sobel_operator#Alternative_operators"/>
/// </summary>
public sealed class ScharrProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="ScharrProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public ScharrProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetector2DProcessor<TPixel>(
configuration,
ScharrKernels.ScharrX,
ScharrKernels.ScharrY,
this.Grayscale,
source,
sourceRectangle);
}
}

31
src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs

@ -1,31 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using the Sobel operator filter.
/// See <see href="http://en.wikipedia.org/wiki/Sobel_operator"/>.
/// </summary>
public sealed class SobelProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="SobelProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public SobelProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetector2DProcessor<TPixel>(
configuration,
SobelKernels.SobelX,
SobelKernels.SobelY,
this.Grayscale,
source,
sourceRectangle);
}
}

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

@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Configuration configuration = this.Source.GetConfiguration(); Configuration configuration = this.Source.GetConfiguration();
// Detect the edges. // Detect the edges.
new SobelProcessor(false).Execute(this.Configuration, temp, this.SourceRectangle); new EdgeDetector2DProcessor(KnownEdgeDetectorKernels.Sobel, false).Execute(this.Configuration, temp, this.SourceRectangle);
// Apply threshold binarization filter. // Apply threshold binarization filter.
new BinaryThresholdProcessor(this.definition.Threshold).Execute(this.Configuration, temp, this.SourceRectangle); new BinaryThresholdProcessor(this.definition.Threshold).Execute(this.Configuration, temp, this.SourceRectangle);

22
tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs

@ -37,17 +37,17 @@ namespace SixLabors.ImageSharp.Benchmarks
[Benchmark(Description = "ImageSharp DetectEdges")] [Benchmark(Description = "ImageSharp DetectEdges")]
public void ImageProcessorCoreDetectEdges() public void ImageProcessorCoreDetectEdges()
{ {
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kayyali)); this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kayyali)); this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kirsch)); this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kirsch));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Laplacian3x3)); this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian3x3));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Laplacian5x5)); this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian5x5));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.LaplacianOfGaussian)); this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.LaplacianOfGaussian));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Prewitt)); this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Prewitt));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.RobertsCross)); this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.RobertsCross));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Robinson)); this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Robinson));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Scharr)); this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Scharr));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Sobel)); this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Sobel));
} }
} }
} }

58
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -4,6 +4,8 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DotNet.RemoteExecutor; using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
@ -103,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)]
public void DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException<TPixel>(TestImageProvider<TPixel> provider) public void Decode_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); provider.LimitAllocatorBufferCapacity().InBytesSqrt(10);
@ -112,6 +114,60 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.IsType<InvalidMemoryOperationException>(ex.InnerException); Assert.IsType<InvalidMemoryOperationException>(ex.InnerException);
} }
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)]
public async Task DecodeAsnc_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InBytesSqrt(10);
InvalidImageContentException ex = await Assert.ThrowsAsync<InvalidImageContentException>(() => provider.GetImageAsync(JpegDecoder));
this.Output.WriteLine(ex.Message);
Assert.IsType<InvalidMemoryOperationException>(ex.InnerException);
}
[Theory]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, 0)]
[InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 1)]
[InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 10)]
[InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 30)]
[InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 1)]
[InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 10)]
[InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 30)]
public async Task Decode_IsCancellable(string fileName, int cancellationDelayMs)
{
// Decoding these huge files took 300ms on i7-8650U in 2020. 30ms should be safe for cancellation delay.
string hugeFile = Path.Combine(
TestEnvironment.InputImagesDirectoryFullPath,
fileName);
var cts = new CancellationTokenSource();
if (cancellationDelayMs == 0)
{
cts.Cancel();
}
else
{
cts.CancelAfter(cancellationDelayMs);
}
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync(hugeFile, cts.Token));
}
[Theory(Skip = "Identify is too fast, doesn't work reliably.")]
[InlineData(TestImages.Jpeg.Baseline.Exif)]
[InlineData(TestImages.Jpeg.Progressive.Bad.ExifUndefType)]
public async Task Identify_IsCancellable(string fileName)
{
string file = Path.Combine(
TestEnvironment.InputImagesDirectoryFullPath,
fileName);
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromTicks(1));
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyAsync(file, cts.Token));
}
// DEBUG ONLY! // DEBUG ONLY!
// The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm" // The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm"
// into "\tests\Images\ActualOutput\JpegDecoderTests\" // into "\tests\Images\ActualOutput\JpegDecoderTests\"

28
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -1,8 +1,11 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
@ -284,5 +287,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
IccProfile values = input.Metadata.IccProfile; IccProfile values = input.Metadata.IccProfile;
Assert.Equal(values.Entries, actual.Entries); Assert.Equal(values.Entries, actual.Entries);
} }
[Theory]
[InlineData(JpegSubsample.Ratio420, 0)]
[InlineData(JpegSubsample.Ratio420, 3)]
[InlineData(JpegSubsample.Ratio420, 10)]
[InlineData(JpegSubsample.Ratio444, 0)]
[InlineData(JpegSubsample.Ratio444, 3)]
[InlineData(JpegSubsample.Ratio444, 10)]
public async Task Encode_IsCancellable(JpegSubsample subsample, int cancellationDelayMs)
{
using var image = new Image<Rgba32>(5000, 5000);
using MemoryStream stream = new MemoryStream();
var cts = new CancellationTokenSource();
if (cancellationDelayMs == 0)
{
cts.Cancel();
}
else
{
cts.CancelAfter(cancellationDelayMs);
}
var encoder = new JpegEncoder() { Subsample = subsample };
await Assert.ThrowsAsync<TaskCanceledException>(() => image.SaveAsync(stream, encoder, cts.Token));
}
} }
} }

2
tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs

@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (var pp = new JpegImagePostProcessor(Configuration.Default, decoder)) using (var pp = new JpegImagePostProcessor(Configuration.Default, decoder))
using (var image = new Image<Rgba32>(decoder.ImageWidth, decoder.ImageHeight)) using (var image = new Image<Rgba32>(decoder.ImageWidth, decoder.ImageHeight))
{ {
pp.PostProcess(image.Frames.RootFrame); pp.PostProcess(image.Frames.RootFrame, default);
image.DebugSave(provider); image.DebugSave(provider);

16
tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs

@ -322,7 +322,21 @@ namespace SixLabors.ImageSharp.Tests.IO
using (var reader = new BufferedReadStream(this.configuration, stream)) using (var reader = new BufferedReadStream(this.configuration, stream))
{ {
Assert.Throws<ArgumentOutOfRangeException>(() => reader.Position = -stream.Length); Assert.Throws<ArgumentOutOfRangeException>(() => reader.Position = -stream.Length);
Assert.Throws<ArgumentOutOfRangeException>(() => reader.Position = stream.Length); Assert.Throws<ArgumentOutOfRangeException>(() => reader.Position = stream.Length + 1);
}
}
}
[Fact]
public void BufferedStreamCanSetPositionToEnd()
{
var bufferSize = 8;
this.configuration.StreamProcessingBufferSize = bufferSize;
using (MemoryStream stream = this.CreateTestStream(bufferSize * 2))
{
using (var reader = new BufferedReadStream(this.configuration, stream))
{
reader.Position = reader.Length;
} }
} }
} }

130
tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs

@ -0,0 +1,130 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
public partial class ImageTests
{
public class Decode_Cancellation : ImageLoadTestBase
{
private bool isTestStreamSeekable;
private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new SemaphoreSlim(0);
private readonly SemaphoreSlim continueSemaphore = new SemaphoreSlim(0);
private readonly CancellationTokenSource cts = new CancellationTokenSource();
public Decode_Cancellation()
{
this.TopLevelConfiguration.StreamProcessingBufferSize = 128;
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task LoadAsync_Specific_Stream(bool isInputStreamSeekable)
{
this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync<Rgb24>(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable)
{
this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
}
[Fact]
public async Task LoadAsync_Agnostic_Path()
{
this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
}
[Fact]
public async Task LoadAsync_Specific_Path()
{
this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync<Rgb24>(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task IdentifyAsync_Stream(bool isInputStreamSeekable)
{
this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
}
[Fact]
public async Task IdentifyAsync_CustomConfiguration_Path()
{
this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable)
{
this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
}
[Fact]
public async Task IdentifyWithFormatAsync_CustomConfiguration_Path()
{
this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
}
[Fact]
public async Task IdentifyWithFormatAsync_DefaultConfiguration_Stream()
{
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyWithFormatAsync(this.DataStream, this.cts.Token));
}
private async Task DoCancel()
{
// wait until we reach the middle of the steam
await this.notifyWaitPositionReachedSemaphore.WaitAsync();
// set the cancellation
this.cts.Cancel();
// continue processing the stream
this.continueSemaphore.Release();
}
protected override Stream CreateStream() => this.TestFormat.CreateAsyncSamaphoreStream(this.notifyWaitPositionReachedSemaphore, this.continueSemaphore, this.isTestStreamSeekable);
}
}
}

2
tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs

@ -24,8 +24,6 @@ namespace SixLabors.ImageSharp.Tests
private ReadOnlySpan<byte> ActualImageSpan => this.ActualImageBytes.AsSpan(); private ReadOnlySpan<byte> ActualImageSpan => this.ActualImageBytes.AsSpan();
private byte[] ByteArray => this.DataStream.ToArray();
private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; private IImageFormat LocalImageFormat => this.localImageFormatMock.Object;
private static readonly IImageFormat ExpectedGlobalFormat = private static readonly IImageFormat ExpectedGlobalFormat =

44
tests/ImageSharp.Tests/Image/ImageTests.Identify.cs

@ -19,9 +19,9 @@ namespace SixLabors.ImageSharp.Tests
{ {
private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F);
private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; private static readonly Size ExpectedImageSize = new Size(108, 202);
private byte[] ByteArray => this.DataStream.ToArray(); private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes;
private IImageInfo LocalImageInfo => this.localImageInfoMock.Object; private IImageInfo LocalImageInfo => this.localImageInfoMock.Object;
@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests
{ {
IImageInfo info = Image.Identify(this.ActualImageBytes, out IImageFormat type); IImageInfo info = Image.Identify(this.ActualImageBytes, out IImageFormat type);
Assert.NotNull(info); Assert.Equal(ExpectedImageSize, info.Size());
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, type);
} }
@ -133,13 +133,45 @@ namespace SixLabors.ImageSharp.Tests
using (var stream = new MemoryStream(this.ActualImageBytes)) using (var stream = new MemoryStream(this.ActualImageBytes))
{ {
var asyncStream = new AsyncStreamWrapper(stream, () => false); var asyncStream = new AsyncStreamWrapper(stream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(asyncStream); (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream);
Assert.NotNull(info.ImageInfo); Assert.Equal(ExpectedImageSize, res.ImageInfo.Size());
Assert.Equal(ExpectedGlobalFormat, info.Format); Assert.Equal(ExpectedGlobalFormat, res.Format);
} }
} }
[Fact]
public async Task FromPathAsync_CustomConfiguration()
{
IImageInfo info = await Image.IdentifyAsync(this.LocalConfiguration, this.MockFilePath);
Assert.Equal(this.LocalImageInfo, info);
}
[Fact]
public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration()
{
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, this.MockFilePath);
Assert.NotNull(info.ImageInfo);
Assert.Equal(this.LocalImageFormat, info.Format);
}
[Fact]
public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration()
{
(IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(ActualImagePath);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size());
Assert.Equal(ExpectedGlobalFormat, res.Format);
}
[Fact]
public async Task FromPathAsync_GlobalConfiguration()
{
IImageInfo info = await Image.IdentifyAsync(ActualImagePath);
Assert.Equal(ExpectedImageSize, info.Size());
}
[Fact] [Fact]
public async Task FromStreamAsync_CustomConfiguration() public async Task FromStreamAsync_CustomConfiguration()
{ {

25
tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs

@ -3,7 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
using Moq; using Moq;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
@ -16,6 +16,8 @@ namespace SixLabors.ImageSharp.Tests
{ {
public abstract class ImageLoadTestBase : IDisposable public abstract class ImageLoadTestBase : IDisposable
{ {
private Lazy<Stream> dataStreamLazy;
protected Image<Rgba32> localStreamReturnImageRgba32; protected Image<Rgba32> localStreamReturnImageRgba32;
protected Image<Bgra4444> localStreamReturnImageAgnostic; protected Image<Bgra4444> localStreamReturnImageAgnostic;
@ -46,10 +48,12 @@ namespace SixLabors.ImageSharp.Tests
public byte[] Marker { get; } public byte[] Marker { get; }
public MemoryStream DataStream { get; } public Stream DataStream => this.dataStreamLazy.Value;
public byte[] DecodedData { get; private set; } public byte[] DecodedData { get; private set; }
protected byte[] ByteArray => ((MemoryStream)this.DataStream).ToArray();
protected ImageLoadTestBase() protected ImageLoadTestBase()
{ {
this.localStreamReturnImageRgba32 = new Image<Rgba32>(1, 1); this.localStreamReturnImageRgba32 = new Image<Rgba32>(1, 1);
@ -60,10 +64,9 @@ namespace SixLabors.ImageSharp.Tests
var detector = new Mock<IImageInfoDetector>(); var detector = new Mock<IImageInfoDetector>();
detector.Setup(x => x.Identify(It.IsAny<Configuration>(), It.IsAny<Stream>())).Returns(this.localImageInfoMock.Object); detector.Setup(x => x.Identify(It.IsAny<Configuration>(), It.IsAny<Stream>())).Returns(this.localImageInfoMock.Object);
detector.Setup(x => x.IdentifyAsync(It.IsAny<Configuration>(), It.IsAny<Stream>())).ReturnsAsync(this.localImageInfoMock.Object); detector.Setup(x => x.IdentifyAsync(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())).ReturnsAsync(this.localImageInfoMock.Object);
this.localDecoder = detector.As<IImageDecoder>();
this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object);
this.localDecoder = detector.As<IImageDecoder>();
this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>())) this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>()))
.Callback<Configuration, Stream>((c, s) => .Callback<Configuration, Stream>((c, s) =>
{ {
@ -86,6 +89,8 @@ namespace SixLabors.ImageSharp.Tests
}) })
.Returns(this.localStreamReturnImageAgnostic); .Returns(this.localStreamReturnImageAgnostic);
this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object);
this.LocalConfiguration = new Configuration(); this.LocalConfiguration = new Configuration();
this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector);
this.LocalConfiguration.ImageFormatsManager.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object); this.LocalConfiguration.ImageFormatsManager.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object);
@ -93,10 +98,12 @@ namespace SixLabors.ImageSharp.Tests
this.TopLevelConfiguration = new Configuration(this.TestFormat); this.TopLevelConfiguration = new Configuration(this.TestFormat);
this.Marker = Guid.NewGuid().ToByteArray(); this.Marker = Guid.NewGuid().ToByteArray();
this.DataStream = this.TestFormat.CreateStream(this.Marker);
this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(this.DataStream); this.dataStreamLazy = new Lazy<Stream>(this.CreateStream);
this.topLevelFileSystem.AddFile(this.MockFilePath, this.DataStream); Stream StreamFactory() => this.DataStream;
this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(StreamFactory);
this.topLevelFileSystem.AddFile(this.MockFilePath, StreamFactory);
this.LocalConfiguration.FileSystem = this.LocalFileSystemMock.Object; this.LocalConfiguration.FileSystem = this.LocalFileSystemMock.Object;
this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem; this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem;
} }
@ -107,6 +114,8 @@ namespace SixLabors.ImageSharp.Tests
this.localStreamReturnImageRgba32?.Dispose(); this.localStreamReturnImageRgba32?.Dispose();
this.localStreamReturnImageAgnostic?.Dispose(); this.localStreamReturnImageAgnostic?.Dispose();
} }
protected virtual Stream CreateStream() => this.TestFormat.CreateStream(this.Marker);
} }
} }
} }

86
tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs

@ -25,84 +25,80 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void Path_Specific() public void Path_Specific()
{ {
using (var img = Image.Load<Rgba32>(this.Path)) using var img = Image.Load<Rgba32>(this.Path);
{ VerifyDecodedImage(img);
VerifyDecodedImage(img);
}
} }
[Fact] [Fact]
public void Path_Agnostic() public void Path_Agnostic()
{ {
using (var img = Image.Load(this.Path)) using var img = Image.Load(this.Path);
{ VerifyDecodedImage(img);
VerifyDecodedImage(img);
}
} }
[Fact] [Fact]
public async Task Path_Agnostic_Async() public async Task Path_Agnostic_Async()
{ {
using (var img = await Image.LoadAsync(this.Path)) using var img = await Image.LoadAsync(this.Path);
{ VerifyDecodedImage(img);
VerifyDecodedImage(img); }
}
[Fact]
public async Task Path_Specific_Async()
{
using var img = await Image.LoadAsync<Rgb24>(this.Path);
VerifyDecodedImage(img);
} }
[Fact] [Fact]
public async Task Path_Agnostic_Configuration_Async() public async Task Path_Agnostic_Configuration_Async()
{ {
using (var img = await Image.LoadAsync(Configuration.Default, this.Path)) using var img = await Image.LoadAsync(this.Path);
{ VerifyDecodedImage(img);
VerifyDecodedImage(img);
}
} }
[Fact] [Fact]
public void Path_Decoder_Specific() public void Path_Decoder_Specific()
{ {
using (var img = Image.Load<Rgba32>(this.Path, new BmpDecoder())) using var img = Image.Load<Rgba32>(this.Path, new BmpDecoder());
{ VerifyDecodedImage(img);
VerifyDecodedImage(img);
}
} }
[Fact] [Fact]
public void Path_Decoder_Agnostic() public void Path_Decoder_Agnostic()
{ {
using (var img = Image.Load(this.Path, new BmpDecoder())) using var img = Image.Load(this.Path, new BmpDecoder());
{ VerifyDecodedImage(img);
VerifyDecodedImage(img);
}
} }
[Fact] [Fact]
public async Task Path_Decoder_Agnostic_Async() public async Task Path_Decoder_Agnostic_Async()
{ {
using (var img = await Image.LoadAsync(Configuration.Default, this.Path, new BmpDecoder())) using var img = await Image.LoadAsync(this.Path, new BmpDecoder());
{ VerifyDecodedImage(img);
VerifyDecodedImage(img); }
}
[Fact]
public async Task Path_Decoder_Specific_Async()
{
using var img = await Image.LoadAsync<Rgb24>(this.Path, new BmpDecoder());
VerifyDecodedImage(img);
} }
[Fact] [Fact]
public void Path_OutFormat_Specific() public void Path_OutFormat_Specific()
{ {
using (var img = Image.Load<Rgba32>(this.Path, out IImageFormat format)) using var img = Image.Load<Rgba32>(this.Path, out IImageFormat format);
{ VerifyDecodedImage(img);
VerifyDecodedImage(img); Assert.IsType<BmpFormat>(format);
Assert.IsType<BmpFormat>(format);
}
} }
[Fact] [Fact]
public void Path_OutFormat_Agnostic() public void Path_OutFormat_Agnostic()
{ {
using (var img = Image.Load(this.Path, out IImageFormat format)) using var img = Image.Load(this.Path, out IImageFormat format);
{ VerifyDecodedImage(img);
VerifyDecodedImage(img); Assert.IsType<BmpFormat>(format);
Assert.IsType<BmpFormat>(format);
}
} }
[Fact] [Fact]
@ -124,6 +120,20 @@ namespace SixLabors.ImageSharp.Tests
Image.Load<Rgba32>((string)null); Image.Load<Rgba32>((string)null);
}); });
} }
[Fact]
public Task Async_WhenFileNotFound_Throws()
{
return Assert.ThrowsAsync<System.IO.FileNotFoundException>(
() => Image.LoadAsync<Rgba32>(Guid.NewGuid().ToString()));
}
[Fact]
public Task Async_WhenPathIsNull_Throws()
{
return Assert.ThrowsAsync<ArgumentNullException>(
() => Image.LoadAsync<Rgba32>((string)null));
}
} }
} }
} }

2
tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs

@ -14,8 +14,6 @@ namespace SixLabors.ImageSharp.Tests
{ {
public class Load_FromBytes_PassLocalConfiguration : ImageLoadTestBase public class Load_FromBytes_PassLocalConfiguration : ImageLoadTestBase
{ {
private byte[] ByteArray => this.DataStream.ToArray();
private ReadOnlySpan<byte> ByteSpan => this.ByteArray.AsSpan(); private ReadOnlySpan<byte> ByteSpan => this.ByteArray.AsSpan();
[Theory] [Theory]

20
tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs

@ -3,9 +3,9 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
using Moq; using Moq;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Xunit; using Xunit;
@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests
[InlineData("test.bmp")] [InlineData("test.bmp")]
[InlineData("test.jpg")] [InlineData("test.jpg")]
[InlineData("test.gif")] [InlineData("test.gif")]
public async Task SaveNeverCallsSyncMethods(string filename) public async Task SaveAsync_NeverCallsSyncMethods(string filename)
{ {
using (var image = new Image<Rgba32>(5, 5)) using (var image = new Image<Rgba32>(5, 5))
{ {
@ -102,6 +102,20 @@ namespace SixLabors.ImageSharp.Tests
} }
} }
} }
[Fact]
public async Task SaveAsync_WithNonSeekableStream_IsCancellable()
{
using var image = new Image<Rgba32>(4000, 4000);
var encoder = new PngEncoder() { CompressionLevel = PngCompressionLevel.BestCompression };
using var stream = new MemoryStream();
var asyncStream = new AsyncStreamWrapper(stream, () => false);
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromTicks(1));
await Assert.ThrowsAnyAsync<TaskCanceledException>(() =>
image.SaveAsync(asyncStream, encoder, cts.Token));
}
} }
} }
} }

2
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -21,7 +21,7 @@
<PackageReference Include="Magick.NET-Q16-AnyCPU" /> <PackageReference Include="Magick.NET-Q16-AnyCPU" />
<PackageReference Include="Microsoft.DotNet.RemoteExecutor" /> <PackageReference Include="Microsoft.DotNet.RemoteExecutor" />
<PackageReference Include="Moq" /> <PackageReference Include="Moq" />
<PackageReference Include="SharpZipLib" /> <PackageReference Include="SharpZipLib" />
<PackageReference Include="System.Drawing.Common" /> <PackageReference Include="System.Drawing.Common" />
</ItemGroup> </ItemGroup>

198
tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs

@ -1,75 +1,201 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Processing.Processors.Convolution;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Convolution namespace SixLabors.ImageSharp.Tests.Processing.Convolution
{ {
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "OK. Used for TheoryData compatibility.")]
public class DetectEdgesTest : BaseImageOperationsExtensionTest public class DetectEdgesTest : BaseImageOperationsExtensionTest
{ {
[Fact] [Fact]
public void DetectEdges_SobelProcessorDefaultsSet() public void DetectEdges_EdgeDetector2DProcessorDefaultsSet()
{ {
this.operations.DetectEdges(); this.operations.DetectEdges();
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>();
// TODO: Enable once we have updated the images
SobelProcessor processor = this.Verify<SobelProcessor>();
Assert.True(processor.Grayscale); Assert.True(processor.Grayscale);
Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel);
} }
[Fact] [Fact]
public void DetectEdges_Rect_SobelProcessorDefaultsSet() public void DetectEdges_Rect_EdgeDetector2DProcessorDefaultsSet()
{ {
this.operations.DetectEdges(this.rect); this.operations.DetectEdges(this.rect);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>(this.rect);
Assert.True(processor.Grayscale);
Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel);
}
public static TheoryData<EdgeDetector2DKernel, bool> EdgeDetector2DKernelData =
new TheoryData<EdgeDetector2DKernel, bool>
{
{ KnownEdgeDetectorKernels.Kayyali, true },
{ KnownEdgeDetectorKernels.Kayyali, false },
{ KnownEdgeDetectorKernels.Prewitt, true },
{ KnownEdgeDetectorKernels.Prewitt, false },
{ KnownEdgeDetectorKernels.RobertsCross, true },
{ KnownEdgeDetectorKernels.RobertsCross, false },
{ KnownEdgeDetectorKernels.Scharr, true },
{ KnownEdgeDetectorKernels.Scharr, false },
{ KnownEdgeDetectorKernels.Sobel, true },
{ KnownEdgeDetectorKernels.Sobel, false },
};
[Theory]
[MemberData(nameof(EdgeDetector2DKernelData))]
public void DetectEdges_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _)
{
this.operations.DetectEdges(kernel);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>();
Assert.True(processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetector2DKernelData))]
public void DetectEdges_Rect_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _)
{
this.operations.DetectEdges(kernel, this.rect);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>(this.rect);
Assert.True(processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetector2DKernelData))]
public void DetectEdges_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale)
{
this.operations.DetectEdges(kernel, grayscale);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>();
Assert.Equal(grayscale, processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetector2DKernelData))]
public void DetectEdges_Rect_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale)
{
this.operations.DetectEdges(kernel, grayscale, this.rect);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>(this.rect);
Assert.Equal(grayscale, processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
public static TheoryData<EdgeDetectorKernel, bool> EdgeDetectorKernelData =
new TheoryData<EdgeDetectorKernel, bool>
{
{ KnownEdgeDetectorKernels.Laplacian3x3, true },
{ KnownEdgeDetectorKernels.Laplacian3x3, false },
{ KnownEdgeDetectorKernels.Laplacian5x5, true },
{ KnownEdgeDetectorKernels.Laplacian5x5, false },
{ KnownEdgeDetectorKernels.LaplacianOfGaussian, true },
{ KnownEdgeDetectorKernels.LaplacianOfGaussian, false },
};
[Theory]
[MemberData(nameof(EdgeDetectorKernelData))]
public void DetectEdges_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _)
{
this.operations.DetectEdges(kernel);
EdgeDetectorProcessor processor = this.Verify<EdgeDetectorProcessor>();
// TODO: Enable once we have updated the images
SobelProcessor processor = this.Verify<SobelProcessor>(this.rect);
Assert.True(processor.Grayscale); Assert.True(processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
} }
public static IEnumerable<object[]> EdgeDetectionTheoryData => new[] [Theory]
[MemberData(nameof(EdgeDetectorKernelData))]
public void DetectEdges_Rect_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _)
{ {
new object[] { new TestType<KayyaliProcessor>(), EdgeDetectionOperators.Kayyali }, this.operations.DetectEdges(kernel, this.rect);
new object[] { new TestType<KirschProcessor>(), EdgeDetectionOperators.Kirsch }, EdgeDetectorProcessor processor = this.Verify<EdgeDetectorProcessor>(this.rect);
new object[] { new TestType<Laplacian3x3Processor>(), EdgeDetectionOperators.Laplacian3x3 },
new object[] { new TestType<Laplacian5x5Processor>(), EdgeDetectionOperators.Laplacian5x5 }, Assert.True(processor.Grayscale);
new object[] { new TestType<LaplacianOfGaussianProcessor>(), EdgeDetectionOperators.LaplacianOfGaussian }, Assert.Equal(kernel, processor.Kernel);
new object[] { new TestType<PrewittProcessor>(), EdgeDetectionOperators.Prewitt }, }
new object[] { new TestType<RobertsCrossProcessor>(), EdgeDetectionOperators.RobertsCross },
new object[] { new TestType<RobinsonProcessor>(), EdgeDetectionOperators.Robinson },
new object[] { new TestType<ScharrProcessor>(), EdgeDetectionOperators.Scharr },
new object[] { new TestType<SobelProcessor>(), EdgeDetectionOperators.Sobel },
};
[Theory] [Theory]
[MemberData(nameof(EdgeDetectionTheoryData))] [MemberData(nameof(EdgeDetectorKernelData))]
public void DetectEdges_filter_SobelProcessorDefaultsSet<TProcessor>(TestType<TProcessor> type, EdgeDetectionOperators filter) public void DetectEdges_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale)
where TProcessor : EdgeDetectorProcessor
{ {
this.operations.DetectEdges(filter); this.operations.DetectEdges(kernel, grayscale);
EdgeDetectorProcessor processor = this.Verify<EdgeDetectorProcessor>();
Assert.Equal(grayscale, processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetectorKernelData))]
public void DetectEdges_Rect_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale)
{
this.operations.DetectEdges(kernel, grayscale, this.rect);
EdgeDetectorProcessor processor = this.Verify<EdgeDetectorProcessor>(this.rect);
Assert.Equal(grayscale, processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
public static TheoryData<EdgeDetectorCompassKernel, bool> EdgeDetectorCompassKernelData =
new TheoryData<EdgeDetectorCompassKernel, bool>
{
{ KnownEdgeDetectorKernels.Kirsch, true },
{ KnownEdgeDetectorKernels.Kirsch, false },
{ KnownEdgeDetectorKernels.Robinson, true },
{ KnownEdgeDetectorKernels.Robinson, false },
};
[Theory]
[MemberData(nameof(EdgeDetectorCompassKernelData))]
public void DetectEdges_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _)
{
this.operations.DetectEdges(kernel);
EdgeDetectorCompassProcessor processor = this.Verify<EdgeDetectorCompassProcessor>();
Assert.True(processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetectorCompassKernelData))]
public void DetectEdges_Rect_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _)
{
this.operations.DetectEdges(kernel, this.rect);
EdgeDetectorCompassProcessor processor = this.Verify<EdgeDetectorCompassProcessor>(this.rect);
// TODO: Enable once we have updated the images
var processor = this.Verify<TProcessor>();
Assert.True(processor.Grayscale); Assert.True(processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetectorCompassKernelData))]
public void DetectEdges_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale)
{
this.operations.DetectEdges(kernel, grayscale);
EdgeDetectorCompassProcessor processor = this.Verify<EdgeDetectorCompassProcessor>();
Assert.Equal(grayscale, processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
} }
[Theory] [Theory]
[MemberData(nameof(EdgeDetectionTheoryData))] [MemberData(nameof(EdgeDetectorCompassKernelData))]
public void DetectEdges_filter_grayscale_SobelProcessorDefaultsSet<TProcessor>(TestType<TProcessor> type, EdgeDetectionOperators filter) public void DetectEdges_Rect_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale)
where TProcessor : EdgeDetectorProcessor
{ {
bool grey = (int)filter % 2 == 0; this.operations.DetectEdges(kernel, grayscale, this.rect);
this.operations.DetectEdges(filter, grey); EdgeDetectorCompassProcessor processor = this.Verify<EdgeDetectorCompassProcessor>(this.rect);
// TODO: Enable once we have updated the images Assert.Equal(grayscale, processor.Grayscale);
var processor = this.Verify<TProcessor>(); Assert.Equal(kernel, processor.Kernel);
Assert.Equal(grey, processor.Grayscale);
} }
} }
} }

81
tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs

@ -0,0 +1,81 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Convolution.Processors
{
public class EdgeDetectorKernelTests
{
[Fact]
public void EdgeDetectorKernelEqualityOperatorTest()
{
EdgeDetectorKernel kernel0 = KnownEdgeDetectorKernels.Laplacian3x3;
EdgeDetectorKernel kernel1 = KnownEdgeDetectorKernels.Laplacian3x3;
EdgeDetectorKernel kernel2 = KnownEdgeDetectorKernels.Laplacian5x5;
Assert.True(kernel0 == kernel1);
Assert.False(kernel0 != kernel1);
Assert.True(kernel0 != kernel2);
Assert.False(kernel0 == kernel2);
Assert.True(kernel0.Equals((object)kernel1));
Assert.True(kernel0.Equals(kernel1));
Assert.False(kernel0.Equals((object)kernel2));
Assert.False(kernel0.Equals(kernel2));
Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode());
Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode());
}
[Fact]
public void EdgeDetector2DKernelEqualityOperatorTest()
{
EdgeDetector2DKernel kernel0 = KnownEdgeDetectorKernels.Prewitt;
EdgeDetector2DKernel kernel1 = KnownEdgeDetectorKernels.Prewitt;
EdgeDetector2DKernel kernel2 = KnownEdgeDetectorKernels.RobertsCross;
Assert.True(kernel0 == kernel1);
Assert.False(kernel0 != kernel1);
Assert.True(kernel0 != kernel2);
Assert.False(kernel0 == kernel2);
Assert.True(kernel0.Equals((object)kernel1));
Assert.True(kernel0.Equals(kernel1));
Assert.False(kernel0.Equals((object)kernel2));
Assert.False(kernel0.Equals(kernel2));
Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode());
Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode());
}
[Fact]
public void EdgeDetectorCompassKernelEqualityOperatorTest()
{
EdgeDetectorCompassKernel kernel0 = KnownEdgeDetectorKernels.Kirsch;
EdgeDetectorCompassKernel kernel1 = KnownEdgeDetectorKernels.Kirsch;
EdgeDetectorCompassKernel kernel2 = KnownEdgeDetectorKernels.Robinson;
Assert.True(kernel0 == kernel1);
Assert.False(kernel0 != kernel1);
Assert.True(kernel0 != kernel2);
Assert.False(kernel0 == kernel2);
Assert.True(kernel0.Equals((object)kernel1));
Assert.True(kernel0.Equals(kernel1));
Assert.False(kernel0.Equals((object)kernel2));
Assert.False(kernel0.Equals(kernel2));
Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode());
Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode());
}
}
}

116
tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs

@ -1,8 +1,10 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit; using Xunit;
@ -10,6 +12,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
{ {
[GroupOutput("Convolution")] [GroupOutput("Convolution")]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "OK. Used for TheoryData compatibility.")]
public class DetectEdgesTest public class DetectEdgesTest
{ {
private static readonly ImageComparer OpaqueComparer = ImageComparer.TolerantPercentage(0.01F); private static readonly ImageComparer OpaqueComparer = ImageComparer.TolerantPercentage(0.01F);
@ -20,18 +23,29 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector;
public static readonly TheoryData<EdgeDetectionOperators> DetectEdgesFilters = new TheoryData<EdgeDetectionOperators> public static readonly TheoryData<EdgeDetectorKernel, string> DetectEdgesFilters
= new TheoryData<EdgeDetectorKernel, string>
{ {
EdgeDetectionOperators.Kayyali, { KnownEdgeDetectorKernels.Laplacian3x3, nameof(KnownEdgeDetectorKernels.Laplacian3x3) },
EdgeDetectionOperators.Kirsch, { KnownEdgeDetectorKernels.Laplacian5x5, nameof(KnownEdgeDetectorKernels.Laplacian5x5) },
EdgeDetectionOperators.Laplacian3x3, { KnownEdgeDetectorKernels.LaplacianOfGaussian, nameof(KnownEdgeDetectorKernels.LaplacianOfGaussian) },
EdgeDetectionOperators.Laplacian5x5, };
EdgeDetectionOperators.LaplacianOfGaussian,
EdgeDetectionOperators.Prewitt, public static readonly TheoryData<EdgeDetector2DKernel, string> DetectEdges2DFilters
EdgeDetectionOperators.RobertsCross, = new TheoryData<EdgeDetector2DKernel, string>
EdgeDetectionOperators.Robinson, {
EdgeDetectionOperators.Scharr, { KnownEdgeDetectorKernels.Kayyali, nameof(KnownEdgeDetectorKernels.Kayyali) },
EdgeDetectionOperators.Sobel { KnownEdgeDetectorKernels.Prewitt, nameof(KnownEdgeDetectorKernels.Prewitt) },
{ KnownEdgeDetectorKernels.RobertsCross, nameof(KnownEdgeDetectorKernels.RobertsCross) },
{ KnownEdgeDetectorKernels.Scharr, nameof(KnownEdgeDetectorKernels.Scharr) },
{ KnownEdgeDetectorKernels.Sobel, nameof(KnownEdgeDetectorKernels.Sobel) },
};
public static readonly TheoryData<EdgeDetectorCompassKernel, string> DetectEdgesCompassFilters
= new TheoryData<EdgeDetectorCompassKernel, string>
{
{ KnownEdgeDetectorKernels.Kirsch, nameof(KnownEdgeDetectorKernels.Kirsch) },
{ KnownEdgeDetectorKernels.Robinson, nameof(KnownEdgeDetectorKernels.Robinson) },
}; };
[Theory] [Theory]
@ -53,7 +67,48 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
[Theory] [Theory]
[WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)]
[WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)]
public void DetectEdges_WorksWithAllFilters<TPixel>(TestImageProvider<TPixel> provider, EdgeDetectionOperators detector) public void DetectEdges_WorksWithAllFilters<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetectorKernel detector,
string name)
where TPixel : unmanaged, IPixel<TPixel>
{
bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern");
ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer;
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.DetectEdges(detector));
image.DebugSave(provider, name);
image.CompareToReferenceOutput(comparer, provider, name);
}
}
[Theory]
[WithTestPatternImages(nameof(DetectEdges2DFilters), 100, 100, PixelTypes.Rgba32)]
[WithFileCollection(nameof(TestImages), nameof(DetectEdges2DFilters), PixelTypes.Rgba32)]
public void DetectEdges2D_WorksWithAllFilters<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetector2DKernel detector,
string name)
where TPixel : unmanaged, IPixel<TPixel>
{
bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern");
ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer;
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.DetectEdges(detector));
image.DebugSave(provider, name);
image.CompareToReferenceOutput(comparer, provider, name);
}
}
[Theory]
[WithTestPatternImages(nameof(DetectEdgesCompassFilters), 100, 100, PixelTypes.Rgba32)]
[WithFileCollection(nameof(TestImages), nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)]
public void DetectEdgesCompass_WorksWithAllFilters<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetectorCompassKernel detector,
string name)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern");
@ -61,8 +116,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
{ {
image.Mutate(x => x.DetectEdges(detector)); image.Mutate(x => x.DetectEdges(detector));
image.DebugSave(provider, detector.ToString()); image.DebugSave(provider, name);
image.CompareToReferenceOutput(comparer, provider, detector.ToString()); image.CompareToReferenceOutput(comparer, provider, name);
} }
} }
@ -115,7 +170,38 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
[Theory] [Theory]
[WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)] [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)]
public void WorksWithDiscoBuffers<TPixel>(TestImageProvider<TPixel> provider, EdgeDetectionOperators detector) public void WorksWithDiscoBuffers<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetectorKernel detector,
string _)
where TPixel : unmanaged, IPixel<TPixel>
{
provider.RunBufferCapacityLimitProcessorTest(
41,
c => c.DetectEdges(detector),
detector);
}
[Theory]
[WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdges2DFilters), PixelTypes.Rgba32)]
public void WorksWithDiscoBuffers2D<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetector2DKernel detector,
string _)
where TPixel : unmanaged, IPixel<TPixel>
{
provider.RunBufferCapacityLimitProcessorTest(
41,
c => c.DetectEdges(detector),
detector);
}
[Theory]
[WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)]
public void WorksWithDiscoBuffersCompass<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetectorCompassKernel detector,
string _)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
provider.RunBufferCapacityLimitProcessorTest( provider.RunBufferCapacityLimitProcessorTest(

10
tests/ImageSharp.Tests/TestFileSystem.cs

@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Tests
/// </summary> /// </summary>
public class TestFileSystem : ImageSharp.IO.IFileSystem public class TestFileSystem : ImageSharp.IO.IFileSystem
{ {
private readonly Dictionary<string, Stream> fileSystem = new Dictionary<string, Stream>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, Func<Stream>> fileSystem = new Dictionary<string, Func<Stream>>(StringComparer.OrdinalIgnoreCase);
public void AddFile(string path, Stream data) public void AddFile(string path, Func<Stream> data)
{ {
lock (this.fileSystem) lock (this.fileSystem)
{ {
@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests
{ {
if (this.fileSystem.ContainsKey(path)) if (this.fileSystem.ContainsKey(path))
{ {
Stream stream = this.fileSystem[path]; Stream stream = this.fileSystem[path]();
stream.Position = 0; stream.Position = 0;
return stream; return stream;
} }
@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests
{ {
if (this.fileSystem.ContainsKey(path)) if (this.fileSystem.ContainsKey(path))
{ {
Stream stream = this.fileSystem[path]; Stream stream = this.fileSystem[path]();
stream.Position = 0; stream.Position = 0;
return stream; return stream;
} }
@ -54,4 +54,4 @@ namespace SixLabors.ImageSharp.Tests
return File.OpenRead(path); return File.OpenRead(path);
} }
} }
} }

41
tests/ImageSharp.Tests/TestFormat.cs

@ -6,9 +6,11 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests
@ -32,9 +34,9 @@ namespace SixLabors.ImageSharp.Tests
public List<DecodeOperation> DecodeCalls { get; } = new List<DecodeOperation>(); public List<DecodeOperation> DecodeCalls { get; } = new List<DecodeOperation>();
public IImageEncoder Encoder { get; } public TestEncoder Encoder { get; }
public IImageDecoder Decoder { get; } public TestDecoder Decoder { get; }
private byte[] header = Guid.NewGuid().ToByteArray(); private byte[] header = Guid.NewGuid().ToByteArray();
@ -52,6 +54,14 @@ namespace SixLabors.ImageSharp.Tests
return ms; return ms;
} }
public Stream CreateAsyncSamaphoreStream(SemaphoreSlim notifyWaitPositionReachedSemaphore, SemaphoreSlim continueSemaphore, bool seeakable, int size = 1024, int waitAfterPosition = 512)
{
var buffer = new byte[size];
this.header.CopyTo(buffer, 0);
var semaphoreStream = new SemaphoreReadMemoryStream(buffer, waitAfterPosition, notifyWaitPositionReachedSemaphore, continueSemaphore);
return seeakable ? (Stream)semaphoreStream : new AsyncStreamWrapper(semaphoreStream, () => false);
}
public void VerifySpecificDecodeCall<TPixel>(byte[] marker, Configuration config) public void VerifySpecificDecodeCall<TPixel>(byte[] marker, Configuration config)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -187,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests
} }
} }
public class TestDecoder : IImageDecoder public class TestDecoder : IImageDecoder, IImageInfoDetector
{ {
private TestFormat testFormat; private TestFormat testFormat;
@ -204,9 +214,17 @@ namespace SixLabors.ImageSharp.Tests
public Image<TPixel> Decode<TPixel>(Configuration config, Stream stream) public Image<TPixel> Decode<TPixel>(Configuration config, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> this.DecodeImpl<TPixel>(config, stream, default).GetAwaiter().GetResult();
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration config, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> this.DecodeImpl<TPixel>(config, stream, cancellationToken);
private async Task<Image<TPixel>> DecodeImpl<TPixel>(Configuration config, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{ {
var ms = new MemoryStream(); var ms = new MemoryStream();
stream.CopyTo(ms); await stream.CopyToAsync(ms, config.StreamProcessingBufferSize, cancellationToken);
var marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); var marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray();
this.testFormat.DecodeCalls.Add(new DecodeOperation this.testFormat.DecodeCalls.Add(new DecodeOperation
{ {
@ -219,15 +237,18 @@ namespace SixLabors.ImageSharp.Tests
return this.testFormat.Sample<TPixel>(); return this.testFormat.Sample<TPixel>();
} }
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration config, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(config, stream));
public bool IsSupportedFileFormat(Span<byte> header) => this.testFormat.IsSupportedFileFormat(header); public bool IsSupportedFileFormat(Span<byte> header) => this.testFormat.IsSupportedFileFormat(header);
public Image Decode(Configuration configuration, Stream stream) => this.Decode<TestPixelForAgnosticDecode>(configuration, stream); public Image Decode(Configuration configuration, Stream stream) => this.Decode<TestPixelForAgnosticDecode>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<TestPixelForAgnosticDecode>(configuration, stream); public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<TestPixelForAgnosticDecode>(configuration, stream, cancellationToken);
public IImageInfo Identify(Configuration configuration, Stream stream) =>
this.IdentifyAsync(configuration, stream, default).GetAwaiter().GetResult();
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeImpl<Rgba32>(configuration, stream, cancellationToken);
} }
public class TestEncoder : ImageSharp.Formats.IImageEncoder public class TestEncoder : ImageSharp.Formats.IImageEncoder
@ -249,7 +270,7 @@ namespace SixLabors.ImageSharp.Tests
// TODO record this happened so we can verify it. // TODO record this happened so we can verify it.
} }
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream) public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// TODO record this happened so we can verify it. // TODO record this happened so we can verify it.

12
tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs

@ -4,8 +4,9 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -164,6 +165,15 @@ namespace SixLabors.ImageSharp.Tests
return cachedImage.Clone(this.Configuration); return cachedImage.Clone(this.Configuration);
} }
public override Task<Image<TPixel>> GetImageAsync(IImageDecoder decoder)
{
Guard.NotNull(decoder, nameof(decoder));
// Used in small subset of decoder tests, no caching.
string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath);
return Image.LoadAsync<TPixel>(this.Configuration, path, decoder);
}
public override void Deserialize(IXunitSerializationInfo info) public override void Deserialize(IXunitSerializationInfo info)
{ {
this.FilePath = info.GetValue<string>("path"); this.FilePath = info.GetValue<string>("path");

6
tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using Castle.Core.Internal; using Castle.Core.Internal;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
@ -97,6 +98,11 @@ namespace SixLabors.ImageSharp.Tests
throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!");
} }
public virtual Task<Image<TPixel>> GetImageAsync(IImageDecoder decoder)
{
throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!");
}
/// <summary> /// <summary>
/// Returns an <see cref="Image{TPixel}"/> instance to the test case with the necessary traits. /// Returns an <see cref="Image{TPixel}"/> instance to the test case with the necessary traits.
/// </summary> /// </summary>

6
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

@ -4,6 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ImageMagick; using ImageMagick;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
@ -46,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
} }
} }
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream) public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(configuration, stream)); => Task.FromResult(this.Decode<TPixel>(configuration, stream));
@ -82,6 +83,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream); public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream); public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);
} }
} }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save