Browse Source

Merge remote-tracking branch 'origin/master' into webp

pull/1552/head
Brian Popow 6 years ago
parent
commit
129a8d183c
  1. 26
      Directory.Build.props
  2. 6
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  3. 4
      src/ImageSharp/Advanced/IImageVisitor.cs
  4. 6
      src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs
  5. 31
      src/ImageSharp/Common/Helpers/SimdUtils.cs
  6. 42
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  7. 5
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  8. 5
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  9. 30
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  10. 48
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  11. 5
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  12. 5
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  13. 23
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  14. 100
      src/ImageSharp/Formats/Gif/ImageExtensions.cs
  15. 7
      src/ImageSharp/Formats/IImageDecoder.cs
  16. 20
      src/ImageSharp/Formats/IImageDecoderInternals.cs
  17. 4
      src/ImageSharp/Formats/IImageEncoder.cs
  18. 25
      src/ImageSharp/Formats/IImageEncoderInternals.cs
  19. 4
      src/ImageSharp/Formats/IImageInfoDetector.cs
  20. 153
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  21. 61
      src/ImageSharp/Formats/ImageEncoderUtilities.cs
  22. 539
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  23. 93
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  24. 27
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  25. 23
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs
  26. 17
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  27. 5
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
  28. 17
      src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs
  29. 37
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs
  30. 13
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  31. 13
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  32. 100
      src/ImageSharp/Formats/Jpeg/ImageExtensions.cs
  33. 56
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  34. 31
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  35. 20
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  36. 123
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  37. 100
      src/ImageSharp/Formats/Png/ImageExtensions.cs
  38. 49
      src/ImageSharp/Formats/Png/PngDecoder.cs
  39. 6
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  40. 9
      src/ImageSharp/Formats/Png/PngEncoder.cs
  41. 30
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  42. 100
      src/ImageSharp/Formats/Tga/ImageExtensions.cs
  43. 48
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  44. 5
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  45. 5
      src/ImageSharp/Formats/Tga/TgaEncoder.cs
  46. 30
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  47. 6
      src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs
  48. 2
      src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs
  49. 4
      src/ImageSharp/Formats/WebP/WebPConstants.cs
  50. 23
      src/ImageSharp/Formats/WebP/WebPDecoder.cs
  51. 6
      src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
  52. 5
      src/ImageSharp/Formats/WebP/WebPEncoder.cs
  53. 12
      src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
  54. 2
      src/ImageSharp/IO/BufferedReadStream.cs
  55. 22
      src/ImageSharp/Image.Decode.cs
  56. 221
      src/ImageSharp/Image.FromFile.cs
  57. 103
      src/ImageSharp/Image.FromStream.cs
  58. 13
      src/ImageSharp/Image.cs
  59. 19
      src/ImageSharp/ImageExtensions.cs
  60. 15
      src/ImageSharp/ImageSharp.csproj
  61. 5
      src/ImageSharp/Image{TPixel}.cs
  62. 12
      src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs
  63. 61
      src/ImageSharp/Processing/EdgeDetectionOperators.cs
  64. 249
      src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs
  65. 63
      src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs
  66. 42
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs
  67. 43
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs
  68. 42
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs
  69. 32
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  70. 25
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs
  71. 28
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs
  72. 31
      src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs
  73. 55
      src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs
  74. 103
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs
  75. 163
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs
  76. 78
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs
  77. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs
  78. 25
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs
  79. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs
  80. 8
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs
  81. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs
  82. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs
  83. 23
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs
  84. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs
  85. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs
  86. 25
      src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs
  87. 25
      src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs
  88. 25
      src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs
  89. 25
      src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs
  90. 31
      src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs
  91. 31
      src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs
  92. 25
      src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs
  93. 31
      src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs
  94. 31
      src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs
  95. 2
      src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
  96. 35
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  97. 52
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs
  98. 18
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  99. 22
      tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs
  100. 2
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs

26
Directory.Build.props

@ -30,17 +30,17 @@
<!--
https://apisof.net/
+===================+=======+==========+=====================+=============+=================+====================+==============+=========+
| SUPPORTS | MATHF | HASHCODE | EXTENDED_INTRINSICS | SPAN_STREAM | ENCODING_STRING | RUNTIME_INTRINSICS | CODECOVERAGE | HOTPATH |
+===================+=======+==========+=====================+=============+=================+====================+==============+=========|
| netcoreapp3.1 | Y | Y | Y | Y | Y | Y | Y | Y |
| netcoreapp2.1 | Y | Y | Y | Y | Y | N | Y | N |
| netcoreapp2.0 | Y | N | N | N | N | N | Y | N |
| netstandard2.1 | Y | Y | N | Y | Y | N | Y | N |
| netstandard2.0 | N | N | N | N | N | N | Y | N |
| netstandard1.3 | N | N | N | N | N | N | N | N |
| net472 | N | N | Y | N | N | N | Y | N |
+===================+=======+==========+=====================+=============+=================+====================+==============+=========|
+===================+=======+==========+=====================+=============+=================+====================+==============+=========+============|
| SUPPORTS | MATHF | HASHCODE | EXTENDED_INTRINSICS | SPAN_STREAM | ENCODING_STRING | RUNTIME_INTRINSICS | CODECOVERAGE | HOTPATH | CREATESPAN |
+===================+=======+==========+=====================+=============+=================+====================+==============+=========|============|
| netcoreapp3.1 | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| netcoreapp2.1 | Y | Y | Y | Y | Y | N | Y | N | Y |
| netcoreapp2.0 | Y | N | N | N | N | N | Y | N | Y |
| netstandard2.1 | Y | Y | N | Y | Y | N | Y | N | Y |
| netstandard2.0 | N | N | N | N | N | N | Y | N | N |
| netstandard1.3 | N | N | N | N | N | N | N | N | N |
| net472 | N | N | Y | N | N | N | Y | N | N |
+===================+=======+==========+=====================+=============+=================+====================+==============+=========|============|
-->
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
@ -52,6 +52,7 @@
<DefineConstants>$(DefineConstants);SUPPORTS_RUNTIME_INTRINSICS</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CODECOVERAGE</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_HOTPATH</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CREATESPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF</DefineConstants>
@ -60,10 +61,12 @@
<DefineConstants>$(DefineConstants);SUPPORTS_SPAN_STREAM</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_ENCODING_STRING</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CODECOVERAGE</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CREATESPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CODECOVERAGE</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CREATESPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF</DefineConstants>
@ -71,6 +74,7 @@
<DefineConstants>$(DefineConstants);SUPPORTS_SPAN_STREAM</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_ENCODING_STRING</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CODECOVERAGE</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_CREATESPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<DefineConstants>$(DefineConstants);SUPPORTS_CODECOVERAGE</DefineConstants>

6
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
@ -73,9 +74,10 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
/// <param name="source">The source image.</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>
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor)
=> source.AcceptAsync(visitor);
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default)
=> source.AcceptAsync(visitor, cancellationToken);
/// <summary>
/// Gets the configuration for the image.

4
src/ImageSharp/Advanced/IImageVisitor.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
@ -31,9 +32,10 @@ namespace SixLabors.ImageSharp.Advanced
/// Provides a pixel-specific implementation for a given operation.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <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>;
}
}

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp
{
@ -32,5 +33,10 @@ namespace SixLabors.ImageSharp
: 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)
{
}
}
}

31
src/ImageSharp/Common/Helpers/SimdUtils.cs

@ -6,6 +6,10 @@ using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp
{
@ -28,7 +32,7 @@ namespace SixLabors.ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector4 PseudoRound(this Vector4 v)
{
var sign = Vector4Utilities.FastClamp(v, new Vector4(-1), new Vector4(1));
Vector4 sign = Vector4Utilities.FastClamp(v, new Vector4(-1), new Vector4(1));
return v + (sign * 0.5f);
}
@ -44,13 +48,24 @@ namespace SixLabors.ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector<float> FastRound(this Vector<float> v)
{
var magic0 = new Vector<int>(int.MinValue); // 0x80000000
Vector<float> sgn0 = Vector.AsVectorSingle(magic0);
Vector<float> and0 = Vector.BitwiseAnd(sgn0, v);
Vector<float> or0 = Vector.BitwiseOr(and0, new Vector<float>(8388608.0f));
Vector<float> add0 = Vector.Add(v, or0);
Vector<float> sub0 = Vector.Subtract(add0, or0);
return sub0;
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
ref Vector256<float> v256 = ref Unsafe.As<Vector<float>, Vector256<float>>(ref v);
Vector256<float> vRound = Avx.RoundToNearestInteger(v256);
return Unsafe.As<Vector256<float>, Vector<float>>(ref vRound);
}
else
#endif
{
var magic0 = new Vector<int>(int.MinValue); // 0x80000000
var sgn0 = Vector.AsVectorSingle(magic0);
var and0 = Vector.BitwiseAnd(sgn0, v);
var or0 = Vector.BitwiseOr(and0, new Vector<float>(8388608.0f));
var add0 = Vector.Add(v, or0);
return Vector.Subtract(add0, or0);
}
}
/// <summary>

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -36,18 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this);
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);
}
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc />
@ -55,46 +45,34 @@ namespace SixLabors.ImageSharp.Formats.Bmp
=> this.Decode<Rgba32>(configuration, stream);
/// <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>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this);
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);
}
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <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/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream);
return new BmpDecoderCore(configuration, this).Identify(bufferedStream);
return new BmpDecoderCore(configuration, this).Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream);
return new BmpDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
}
}
}

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

@ -6,6 +6,7 @@ using System.Buffers;
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -118,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
try
@ -197,7 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream)
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ReadImageHeaders(stream, out _, out _);
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.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@ -42,11 +43,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <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>
{
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.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Image encoder for writing an image to a stream as a Windows bitmap.
/// </summary>
internal sealed class BmpEncoderCore
internal sealed class BmpEncoderCore : IImageEncoderInternals
{
/// <summary>
/// The amount to pad each row by.
@ -97,32 +98,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <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 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>
{
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(stream, nameof(stream));

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -30,21 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
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;
}
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc />
@ -52,30 +39,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
=> this.Decode<Rgba32>(configuration, stream);
/// <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>
{
var decoder = new GifDecoderCore(configuration, this);
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;
}
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <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/>
public IImageInfo Identify(Configuration configuration, Stream stream)
@ -85,18 +59,16 @@ namespace SixLabors.ImageSharp.Formats.Gif
var decoder = new GifDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Identify(bufferedStream);
return decoder.Identify(bufferedStream, default);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.IdentifyAsync(bufferedStream);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
}
}
}

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

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

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@ -41,11 +42,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
/// <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>
{
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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
@ -18,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Implements the GIF encoding protocol.
/// </summary>
internal sealed class GifEncoderCore
internal sealed class GifEncoderCore : IImageEncoderInternals
{
/// <summary>
/// Used for allocating memory during processing operations.
@ -75,25 +76,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <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 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>
{
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(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.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
@ -38,9 +39,10 @@ namespace SixLabors.ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>The <see cref="Image{TPixel}"/>.</returns>
// 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>;
/// <summary>
@ -48,8 +50,9 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
/// <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>The <see cref="Image"/>.</returns>
// 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.
using System;
using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
@ -17,21 +18,36 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
Configuration Configuration { get; }
/// <summary>
/// Gets the dimensions of the image being decoded.
/// </summary>
Size Dimensions { get; }
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
/// <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="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <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>;
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <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>
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.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
@ -27,8 +28,9 @@ namespace SixLabors.ImageSharp.Formats
/// <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>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <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>;
}
}

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.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SixLabors.ImageSharp.Formats
@ -24,7 +25,8 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
/// <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>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.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
@ -14,21 +17,159 @@ namespace SixLabors.ImageSharp.Formats
/// Reads the raw image information from the specified stream.
/// </summary>
/// <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>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<IImageInfo> IdentifyAsync(this IImageDecoderInternals decoder, BufferedReadStream stream)
=> Task.FromResult(decoder.Identify(stream));
public static Task<IImageInfo> IdentifyAsync(
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>
/// Decodes the image from the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
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>
=> 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.
// 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;
<#
var formats = new []{
"Bmp",
"Gif",
"Jpeg",
"Png",
"Tga",
};
foreach (string fmt in formats)
{
#>
using SixLabors.ImageSharp.Formats.<#= fmt #>;
<#
}
#>
namespace SixLabors.ImageSharp
{
@ -13,88 +35,115 @@ namespace SixLabors.ImageSharp
/// </summary>
public static partial class ImageExtensions
{
<#
foreach (string fmt in formats)
{
#>
/// <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>
/// <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>
/// 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>
/// <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);
public static Task SaveAs<#= fmt #>Async(this Image source, string path) => SaveAs<#= fmt #>Async(source, path, null);
/// <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>
/// <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) =>
public static void SaveAs<#= fmt #>(this Image source, string path, <#= fmt #>Encoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance));
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
/// <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="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) =>
public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance));
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance),
cancellationToken);
/// <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="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);
public static void SaveAs<#= fmt #>(this Image source, Stream stream)
=> SaveAs<#= fmt #>(source, stream, null);
/// <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="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) => 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>
/// 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="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 SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) =>
source.Save(
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAs<#= fmt #>(this Image source, Stream stream, <#= fmt #>Encoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance));
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
/// <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="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) =>
public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance));
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance),
cancellationToken);
<#
}
#>
}
}

27
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -376,22 +376,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="block">Source block</param>
/// <param name="dest">Destination block</param>
/// <param name="qt">The quantization table</param>
/// <param name="unzigPtr">Pointer to elements of <see cref="ZigZag"/></param>
/// <param name="unZig">The 8x8 Unzig block.</param>
public static unsafe void Quantize(
Block8x8F* block,
Block8x8F* dest,
Block8x8F* qt,
byte* unzigPtr)
ref Block8x8F block,
ref Block8x8F dest,
ref Block8x8F qt,
ref ZigZag unZig)
{
float* s = (float*)block;
float* d = (float*)dest;
for (int zig = 0; zig < Size; zig++)
{
d[zig] = s[unzigPtr[zig]];
dest[zig] = block[unZig[zig]];
}
DivideRoundAll(ref *dest, ref *qt);
DivideRoundAll(ref dest, ref qt);
}
/// <summary>
@ -399,14 +396,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary>
/// <param name="destination">The destination block.</param>
/// <param name="source">The source block.</param>
public static unsafe void Scale16X16To8X8(Block8x8F* destination, Block8x8F* source)
public static unsafe void Scale16X16To8X8(ref Block8x8F destination, ReadOnlySpan<Block8x8F> source)
{
float* d = (float*)destination;
for (int i = 0; i < 4; i++)
{
int dstOff = ((i & 2) << 4) | ((i & 1) << 2);
float* iSource = (float*)(source + i);
Block8x8F iSource = source[i];
for (int y = 0; y < 4; y++)
{
@ -414,7 +409,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
int j = (16 * y) + (2 * x);
float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9];
d[(8 * y) + x + dstOff] = (sum + 2) / 4;
destination[(8 * y) + x + dstOff] = (sum + 2) * .25F;
}
}
}
@ -589,7 +584,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{
// sign(dividend) = max(min(dividend, 1), -1)
var sign = Vector4Utilities.FastClamp(dividend, NegativeOne, Vector4.One);
Vector4 sign = Vector4Utilities.FastClamp(dividend, NegativeOne, Vector4.One);
// AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend)
return (dividend / divisor) + (sign * Offset);

23
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs

@ -93,25 +93,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public unsafe int DecodeHuffman(ref HuffmanTable h)
{
this.CheckBits();
int v = this.PeekBits(JpegConstants.Huffman.LookupBits);
int symbol = h.LookaheadValue[v];
int size = h.LookaheadSize[v];
int index = this.PeekBits(JpegConstants.Huffman.LookupBits);
int size = h.LookaheadSize[index];
if (size == JpegConstants.Huffman.SlowBits)
if (size < JpegConstants.Huffman.SlowBits)
{
ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remainingBits);
while (x > h.MaxCode[size])
{
size++;
}
this.remainingBits -= size;
return h.LookaheadValue[index];
}
v = (int)(x >> (JpegConstants.Huffman.RegisterSize - size));
symbol = h.Values[(h.ValOffset[size] + v) & 0xFF];
ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remainingBits);
while (x > h.MaxCode[size])
{
size++;
}
this.remainingBits -= size;
return symbol;
return h.Values[(h.ValOffset[size] + (int)(x >> (JpegConstants.Huffman.RegisterSize - size))) & 0xFF];
}
[MethodImpl(InliningOptions.ShortMethod)]

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

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

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

@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.Numerics;
using System.Threading;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -111,7 +112,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <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>
{
this.PixelRowCounter = 0;
@ -123,6 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height)
{
cancellationToken.ThrowIfCancellationRequested();
this.DoPostProcessorStep(destination);
}
}

17
src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs

@ -1,17 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Poor man's stackalloc: Contains a value-type <see cref="float"/> buffer sized for 4 <see cref="Block8x8F"/> instances.
/// Useful for decoder/encoder operations allocating a block for each Jpeg component.
/// </summary>
internal unsafe struct BlockQuad
{
/// <summary>
/// The value-type <see cref="float"/> buffer sized for 4 <see cref="Block8x8F"/> instances.
/// </summary>
public fixed float Data[4 * Block8x8F.Size];
}
}

37
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
@ -96,30 +96,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ConvertPixelInto(int r, int g, int b, ref float yResult, ref float cbResult, ref float crResult)
public void ConvertPixelInto(
int r,
int g,
int b,
ref Block8x8F yResult,
ref Block8x8F cbResult,
ref Block8x8F crResult,
int i)
{
ref int start = ref Unsafe.As<RgbToYCbCrTables, int>(ref this);
// float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
yResult[i] = (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits;
ref int yR = ref start;
ref int yG = ref Unsafe.Add(ref start, 256 * 1);
ref int yB = ref Unsafe.Add(ref start, 256 * 2);
// float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits;
ref int cbR = ref Unsafe.Add(ref start, 256 * 3);
ref int cbG = ref Unsafe.Add(ref start, 256 * 4);
ref int cbB = ref Unsafe.Add(ref start, 256 * 5);
ref int crG = ref Unsafe.Add(ref start, 256 * 6);
ref int crB = ref Unsafe.Add(ref start, 256 * 7);
yResult = (Unsafe.Add(ref yR, r) + Unsafe.Add(ref yG, g) + Unsafe.Add(ref yB, b)) >> ScaleBits;
cbResult = (Unsafe.Add(ref cbR, r) + Unsafe.Add(ref cbG, g) + Unsafe.Add(ref cbB, b)) >> ScaleBits;
crResult = (Unsafe.Add(ref cbB, r) + Unsafe.Add(ref crG, g) + Unsafe.Add(ref crB, b)) >> ScaleBits;
// float cr = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Fix(float x)
{
return (int)((x * (1L << ScaleBits)) + 0.5F);
}
=> (int)((x * (1L << ScaleBits)) + 0.5F);
}
}
}

13
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs

@ -62,9 +62,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
Span<Rgb24> rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan);
ref float yBlockStart = ref Unsafe.As<Block8x8F, float>(ref this.Y);
ref float cbBlockStart = ref Unsafe.As<Block8x8F, float>(ref this.Cb);
ref float crBlockStart = ref Unsafe.As<Block8x8F, float>(ref this.Cr);
ref Block8x8F yBlock = ref this.Y;
ref Block8x8F cbBlock = ref this.Cb;
ref Block8x8F crBlock = ref this.Cr;
ref Rgb24 rgbStart = ref rgbSpan[0];
for (int i = 0; i < 64; i++)
@ -75,9 +75,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
c.R,
c.G,
c.B,
ref Unsafe.Add(ref yBlockStart, i),
ref Unsafe.Add(ref cbBlockStart, i),
ref Unsafe.Add(ref crBlockStart, i));
ref yBlock,
ref cbBlock,
ref crBlock,
i);
}
}
}

13
src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs

@ -4,7 +4,6 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -16,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct GenericBlock8x8<T>
where T : struct
where T : unmanaged
{
public const int Size = 64;
@ -110,6 +109,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <summary>
/// Only for on-stack instances!
/// </summary>
public Span<T> AsSpanUnsafe() => new Span<T>(Unsafe.AsPointer(ref this), Size);
public Span<T> AsSpanUnsafe()
{
#if SUPPORTS_CREATESPAN
Span<GenericBlock8x8<T>> s = MemoryMarshal.CreateSpan(ref this, 1);
return MemoryMarshal.Cast<GenericBlock8x8<T>, T>(s);
#else
return new Span<T>(Unsafe.AsPointer(ref this), Size);
#endif
}
}
}

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.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -24,20 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
try
{
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;
}
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc />
@ -45,31 +33,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
=> this.Decode<Rgba32>(configuration, stream);
/// <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>
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
try
{
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;
}
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <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/>
public IImageInfo Identify(Configuration configuration, Stream stream)
@ -77,20 +53,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Identify(bufferedStream);
return decoder.Identify(configuration, stream);
}
/// <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));
using var decoder = new JpegDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.IdentifyAsync(bufferedStream);
// 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 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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
@ -117,6 +118,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/>
public Size ImageSizeInPixels { get; private set; }
/// <inheritdoc/>
Size IImageDecoderInternals.Dimensions => this.ImageSizeInPixels;
/// <summary>
/// Gets the number of MCU blocks in the image as <see cref="Size"/>.
/// </summary>
@ -205,21 +209,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.ParseStream(stream);
this.ParseStream(stream, cancellationToken: cancellationToken);
this.InitExifProfile();
this.InitIccProfile();
this.InitIptcProfile();
this.InitDerivedMetadataProperties();
return this.PostProcessIntoImage<TPixel>();
return this.PostProcessIntoImage<TPixel>(cancellationToken);
}
/// <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.InitIccProfile();
this.InitIptcProfile();
@ -233,7 +237,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="stream">The input stream</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();
@ -263,6 +268,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
while (fileMarker.Marker != JpegConstants.Markers.EOI
|| (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{
cancellationToken.ThrowIfCancellationRequested();
if (!fileMarker.Invalid)
{
// Get the marker length
@ -279,7 +286,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.SOS:
if (!metadataOnly)
{
this.ProcessStartOfScanMarker(stream);
this.ProcessStartOfScanMarker(stream, cancellationToken);
break;
}
else
@ -996,8 +1003,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Processes the SOS (Start of scan marker).
/// </summary>
/// <param name="stream">The input stream.</param>
private void ProcessStartOfScanMarker(BufferedReadStream stream)
private void ProcessStartOfScanMarker(BufferedReadStream stream, CancellationToken cancellationToken)
{
if (this.Frame is null)
{
@ -1048,7 +1054,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
spectralStart,
spectralEnd,
successiveApproximation >> 4,
successiveApproximation & 15);
successiveApproximation & 15,
cancellationToken);
sd.ParseEntropyCodedData();
}
@ -1081,7 +1088,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
private Image<TPixel> PostProcessIntoImage<TPixel>()
private Image<TPixel> PostProcessIntoImage<TPixel>(CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
if (this.ImageWidth == 0 || this.ImageHeight == 0)
@ -1097,7 +1104,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
using (var postProcessor = new JpegImagePostProcessor(this.Configuration, this))
{
postProcessor.PostProcess(image.Frames.RootFrame);
postProcessor.PostProcess(image.Frames.RootFrame, cancellationToken);
}
return image;

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
@ -43,26 +44,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <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>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <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>
{
var encoder = new JpegEncoderCore(this);
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);
}
}
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
}

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

@ -6,7 +6,7 @@ using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Image encoder for writing an image to a stream as a jpeg.
/// </summary>
internal sealed unsafe class JpegEncoderCore
internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals
{
/// <summary>
/// The number of quantization tables.
@ -194,11 +194,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image to write from.</param>
/// <param name="stream">The stream to write to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
/// <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>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
cancellationToken.ThrowIfCancellationRequested();
const ushort max = JpegConstants.MaxLength;
if (image.Width >= max || image.Height >= max)
@ -247,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteDefineHuffmanTables(componentCount);
// Write the image data.
this.WriteStartOfScan(image);
this.WriteStartOfScan(image, cancellationToken);
// Write the End Of Image marker.
this.buffer[0] = JpegConstants.Markers.XFF;
@ -396,7 +398,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -418,6 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
var currentRows = new RowOctet<TPixel>(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8)
@ -427,27 +431,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
&pixelConverter.Y,
&temp1,
&temp2,
&onStackLuminanceQuantTable,
unzig.Data);
ref pixelConverter.Y,
ref temp1,
ref temp2,
ref onStackLuminanceQuantTable,
ref unzig);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
&pixelConverter.Cb,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
ref pixelConverter.Cb,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
&pixelConverter.Cr,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
ref pixelConverter.Cr,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
}
}
}
@ -512,25 +516,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="tempDest1">Temporal block to be used as FDCT Destination</param>
/// <param name="tempDest2">Temporal block 2</param>
/// <param name="quant">Quantization table</param>
/// <param name="unzigPtr">The 8x8 Unzig block pointer</param>
/// <param name="unZig">The 8x8 Unzig block.</param>
/// <returns>
/// The <see cref="int"/>
/// </returns>
private int WriteBlock(
QuantIndex index,
int prevDC,
Block8x8F* src,
Block8x8F* tempDest1,
Block8x8F* tempDest2,
Block8x8F* quant,
byte* unzigPtr)
ref Block8x8F src,
ref Block8x8F tempDest1,
ref Block8x8F tempDest2,
ref Block8x8F quant,
ref ZigZag unZig)
{
FastFloatingPointDCT.TransformFDCT(ref *src, ref *tempDest1, ref *tempDest2);
FastFloatingPointDCT.TransformFDCT(ref src, ref tempDest1, ref tempDest2);
Block8x8F.Quantize(tempDest1, tempDest2, quant, unzigPtr);
float* unziggedDestPtr = (float*)tempDest2;
Block8x8F.Quantize(ref tempDest1, ref tempDest2, ref quant, ref unZig);
int dc = (int)unziggedDestPtr[0];
int dc = (int)tempDest2[0];
// Emit the DC delta.
this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC);
@ -541,7 +544,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int zig = 1; zig < Block8x8F.Size; zig++)
{
int ac = (int)unziggedDestPtr[zig];
int ac = (int)tempDest2[zig];
if (ac == 0)
{
@ -943,7 +946,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -953,10 +957,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
switch (this.subsample)
{
case JpegSubsample.Ratio444:
this.Encode444(image);
this.Encode444(image, cancellationToken);
break;
case JpegSubsample.Ratio420:
this.Encode420(image);
this.Encode420(image, cancellationToken);
break;
}
@ -970,16 +974,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
Block8x8F b = default;
BlockQuad cb = default;
BlockQuad cr = default;
var cbPtr = (Block8x8F*)cb.Data;
var crPtr = (Block8x8F*)cr.Data;
Span<Block8x8F> cb = stackalloc Block8x8F[4];
Span<Block8x8F> cr = stackalloc Block8x8F[4];
Block8x8F temp1 = default;
Block8x8F temp2 = default;
@ -998,6 +1000,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int y = 0; y < pixels.Height; y += 16)
{
cancellationToken.ThrowIfCancellationRequested();
for (int x = 0; x < pixels.Width; x += 16)
{
for (int i = 0; i < 4; i++)
@ -1010,38 +1013,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows);
cbPtr[i] = pixelConverter.Cb;
crPtr[i] = pixelConverter.Cr;
cb[i] = pixelConverter.Cb;
cr[i] = pixelConverter.Cr;
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
&pixelConverter.Y,
&temp1,
&temp2,
&onStackLuminanceQuantTable,
unzig.Data);
ref pixelConverter.Y,
ref temp1,
ref temp2,
ref onStackLuminanceQuantTable,
ref unzig);
}
Block8x8F.Scale16X16To8X8(&b, cbPtr);
Block8x8F.Scale16X16To8X8(ref b, cb);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
&b,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
ref b,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
Block8x8F.Scale16X16To8X8(&b, crPtr);
Block8x8F.Scale16X16To8X8(ref b, cr);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
&b,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
ref b,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig);
}
}
}

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.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png
@ -22,65 +21,37 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
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;
}
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <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>
{
var decoder = new PngDecoderCore(configuration, this);
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;
}
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <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/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
var decoder = new PngDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Identify(bufferedStream);
return decoder.Identify(configuration, stream);
}
/// <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);
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.IdentifyAsync(bufferedStream);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
}
}
}

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

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

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@ -71,13 +72,17 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <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>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <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>
{
// 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)))
{
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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png.Chunks;
@ -21,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Performs the png encoding operation.
/// </summary>
internal sealed class PngEncoderCore : IDisposable
internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
{
/// <summary>
/// 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>
/// <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 async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
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="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)
/// <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>
{
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.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -21,21 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this);
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;
}
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc />
@ -43,49 +30,34 @@ namespace SixLabors.ImageSharp.Formats.Tga
=> this.Decode<Rgba32>(configuration, stream);
/// <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>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this);
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;
}
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <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/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream);
return new TgaDecoderCore(configuration, this).Identify(bufferedStream);
return new TgaDecoderCore(configuration, this).Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream);
return new TgaDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
return new TgaDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
}
}
}

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

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

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@ -32,11 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
/// <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>
{
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.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
@ -16,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <summary>
/// Image encoder for writing an image to a stream as a truevision targa image.
/// </summary>
internal sealed class TgaEncoderCore
internal sealed class TgaEncoderCore : IImageEncoderInternals
{
/// <summary>
/// Used for allocating memory during processing operations.
@ -61,31 +62,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <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 async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
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)
/// <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>
{
Guard.NotNull(image, nameof(image));

6
src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs

@ -9,10 +9,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
internal interface IWebPEncoderOptions
{
/// <summary>
/// Gets a value indicating whether lossless compression should be used.
/// If false, lossy compression will be used.
/// Gets a value indicating whether lossy compression should be used.
/// If false, lossless compression will be used.
/// </summary>
bool Lossless { get; }
bool Lossy { get; }
/// <summary>
/// Gets the compression quality. Between 0 and 100.

2
src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs

@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
stride = 0;
uint limit = counts[0];
uint sum = 0;
for (int i = 0; i < length + 1; i++)
for (int i = 0; i < length; i++)
{
var valuesShouldBeCollapsedToStrideAverage = ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit);
if (i == length || goodForRle[i] || (i != 0 && goodForRle[i - 1]) || !valuesShouldBeCollapsedToStrideAverage)

4
src/ImageSharp/Formats/WebP/WebPConstants.cs

@ -114,9 +114,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
public const int MaxPaletteSize = 256;
/// <summary>
/// Maximum number of color cache bits.
/// Maximum number of color cache bits is 11.
/// </summary>
public const int MaxColorCacheBits = 10;
public const int MaxColorCacheBits = 11;
/// <summary>
/// The maximum number of allowed transforms in a VP8L bitstream.

23
src/ImageSharp/Formats/WebP/WebPDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
@ -30,14 +31,13 @@ namespace SixLabors.ImageSharp.Formats.WebP
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Decode<TPixel>(bufferedStream);
return decoder.Decode<TPixel>(configuration, stream);
}
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);
throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
}
}
@ -46,15 +46,14 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream);
return new WebPDecoderCore(configuration, this).Identify(bufferedStream);
return new WebPDecoderCore(configuration, this).Identify(configuration, stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
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>
{
Guard.NotNull(stream, nameof(stream));
@ -64,27 +63,27 @@ namespace SixLabors.ImageSharp.Formats.WebP
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.DecodeAsync<TPixel>(bufferedStream);
return decoder.DecodeAsync<TPixel>(configuration, bufferedStream, cancellationToken);
}
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);
throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
}
}
/// <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 />
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream);
return new WebPDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
return new WebPDecoderCore(configuration, this).IdentifyAsync(configuration, bufferedStream, cancellationToken);
}
}
}

6
src/ImageSharp/Formats/WebP/WebPDecoderCore.cs

@ -4,7 +4,7 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.Formats.WebP.BitReader;
using SixLabors.ImageSharp.Formats.WebP.Lossless;
using SixLabors.ImageSharp.Formats.WebP.Lossy;
@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
public Size Dimensions => new Size((int)this.webImageInfo.Width, (int)this.webImageInfo.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.Metadata = new ImageMetadata();
@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream)
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.currentStream = stream;

5
src/ImageSharp/Formats/WebP/WebPEncoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@ -14,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
public sealed class WebPEncoder : IImageEncoder, IWebPEncoderOptions
{
/// <inheritdoc/>
public bool Lossless { get; set; }
public bool Lossy { get; set; }
/// <inheritdoc/>
public float Quality { get; set; }
@ -37,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
/// <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>
{
var encoder = new WebPEncoderCore(this, image.GetMemoryAllocator());

12
src/ImageSharp/Formats/WebP/WebPEncoderCore.cs

@ -34,9 +34,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
private bool alphaCompression;
/// <summary>
/// Indicating whether lossless compression should be used. If false, lossy compression will be used.
/// Indicating whether lossy compression should be used. If false, lossless compression will be used.
/// </summary>
private bool lossless;
private readonly bool lossy;
/// <summary>
/// Compression quality. Between 0 and 100.
@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
this.memoryAllocator = memoryAllocator;
this.alphaCompression = options.AlphaCompression;
this.lossless = options.Lossless;
this.lossy = options.Lossy;
this.quality = options.Quality;
}
@ -71,14 +71,14 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.configuration = image.GetConfiguration();
ImageMetadata metadata = image.Metadata;
if (this.lossless)
if (this.lossy)
{
var enc = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height);
var enc = new Vp8Encoder(this.memoryAllocator, image.Width, image.Height);
enc.Encode(image, stream);
}
else
{
var enc = new Vp8Encoder(this.memoryAllocator, image.Width, image.Height);
var enc = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height);
enc.Encode(image, stream);
}
}

2
src/ImageSharp/IO/BufferedReadStream.cs

@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.IO
set
{
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
// otherwise we should simply move the value by the diff.

22
src/ImageSharp/Image.Decode.cs

@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
@ -156,18 +157,24 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream.</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>
/// <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>
{
(IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false);
(IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config)
.ConfigureAwait(false);
if (decoder is 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);
}
@ -183,7 +190,7 @@ namespace SixLabors.ImageSharp
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);
if (decoder is null)
@ -191,7 +198,7 @@ namespace SixLabors.ImageSharp
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);
}
@ -221,11 +228,12 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="config">the configuration.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>
/// 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
/// 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);
@ -239,7 +247,7 @@ namespace SixLabors.ImageSharp
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);
}
}

221
src/ImageSharp/Image.FromFile.cs

@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
@ -80,15 +81,75 @@ namespace SixLabors.ImageSharp
}
/// <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>
/// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(string path)
=> Load(Configuration.Default, path);
/// <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> 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>
/// Create a new instance of the <see cref="Image"/> class from the given file.
@ -97,9 +158,9 @@ namespace SixLabors.ImageSharp
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(string path)
=> LoadAsync(Configuration.Default, path);
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(string path)
=> Load(Configuration.Default, path);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
@ -131,18 +192,21 @@ namespace SixLabors.ImageSharp
/// </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>
/// <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))
{
(Image img, _) = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false);
return img;
}
using Stream stream = configuration.FileSystem.OpenRead(path);
(Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken)
.ConfigureAwait(false);
return img;
}
/// <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>
/// 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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return LoadAsync(configuration, stream, decoder);
}
using Stream stream = configuration.FileSystem.OpenRead(path);
return LoadAsync<TPixel>(configuration, stream, decoder, 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>
/// <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>

103
src/ImageSharp/Image.FromStream.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
@ -62,7 +63,8 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
configuration,
stream,
s => InternalDetectFormatAsync(s, configuration));
(s, _) => InternalDetectFormatAsync(s, configuration),
default);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
@ -125,6 +127,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="configuration">The configuration.</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 stream is null.</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 suitable detector is not found.
/// </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;
}
@ -164,35 +170,43 @@ namespace SixLabors.ImageSharp
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <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 stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// A <see cref="Task"/> representing the asynchronous operation or null if
/// a suitable detector is not found.
/// 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(Stream stream)
=> IdentifyWithFormatAsync(Configuration.Default, stream);
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
Stream stream,
CancellationToken cancellationToken = default)
=> IdentifyWithFormatAsync(Configuration.Default, stream, cancellationToken);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</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 stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <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.
/// </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(
configuration,
stream,
s => InternalIdentityAsync(s, configuration ?? Configuration.Default));
(s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct),
cancellationToken);
/// <summary>
/// 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="stream">The stream containing image information.</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 stream 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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));
return WithSeekableStreamAsync(
configuration,
stream,
s => decoder.DecodeAsync(configuration, s));
(s, ct) => decoder.DecodeAsync(configuration, s, ct),
cancellationToken);
}
/// <summary>
@ -336,15 +356,17 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="configuration">The configuration for the decoder.</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 stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</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 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;
}
@ -425,18 +447,20 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream containing image information.</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="NotSupportedException">The stream is not readable.</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"/> 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>
=> WithSeekableStreamAsync(
Configuration.Default,
stream,
s => decoder.DecodeAsync<TPixel>(Configuration.Default, s));
(s, ct) => decoder.DecodeAsync<TPixel>(Configuration.Default, s, ct),
cancellationToken);
/// <summary>
/// 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="stream">The stream containing image information.</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 stream is null.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
=> WithSeekableStreamAsync(
configuration,
stream,
s => decoder.DecodeAsync<TPixel>(configuration, s));
(s, ct) => decoder.DecodeAsync<TPixel>(configuration, s, ct),
cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -532,18 +562,23 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="configuration">The configuration options.</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 stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <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(
configuration,
stream,
async s => await DecodeAsync(s, configuration).ConfigureAwait(false))
async (s, ct) => await DecodeAsync(s, configuration, ct).ConfigureAwait(false),
cancellationToken)
.ConfigureAwait(false);
if (data.Image != null)
@ -567,6 +602,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="configuration">The configuration options.</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 stream is null.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
(Image<TPixel> Image, IImageFormat Format) data =
await WithSeekableStreamAsync(
configuration,
stream,
s => DecodeAsync<TPixel>(s, configuration))
(s, ct) => DecodeAsync<TPixel>(s, configuration, ct),
cancellationToken)
.ConfigureAwait(false);
if (data.Image != null)
@ -605,6 +645,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="configuration">The configuration options.</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 stream is null.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
(Image<TPixel> img, _) = await LoadWithFormatAsync<TPixel>(configuration, stream).ConfigureAwait(false);
(Image<TPixel> img, _) = await LoadWithFormatAsync<TPixel>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
return img;
}
@ -700,11 +745,13 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The input stream.</param>
/// <param name="action">The action to perform.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The <see cref="Task{T}"/>.</returns>
private static async Task<T> WithSeekableStreamAsync<T>(
Configuration configuration,
Stream stream,
Func<Stream, Task<T>> action)
Func<Stream, CancellationToken, Task<T>> action,
CancellationToken cancellationToken)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(stream, nameof(stream));
@ -725,14 +772,14 @@ namespace SixLabors.ImageSharp
stream.Position = 0;
}
return await action(stream).ConfigureAwait(false);
return await action(stream, cancellationToken).ConfigureAwait(false);
}
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;
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.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
@ -103,15 +104,16 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <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="ArgumentNullException">Thrown if the stream or encoder is null.</exception>
/// <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(encoder, nameof(encoder));
this.EnsureNotDisposed();
return this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream));
return this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream), cancellationToken);
}
/// <summary>
@ -162,7 +164,8 @@ namespace SixLabors.ImageSharp
/// with the pixel type of the image.
/// </summary>
/// <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
{
@ -179,8 +182,8 @@ namespace SixLabors.ImageSharp
public void Visit<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> => this.encoder.Encode(image, this.stream);
public Task VisitAsync<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> => this.encoder.EncodeAsync(image, this.stream);
public Task VisitAsync<TPixel>(Image<TPixel> image, CancellationToken cancellationToken)
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.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
@ -30,10 +31,11 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="source">The source image.</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>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsync(this Image source, string path)
=> source.SaveAsync(path, source.DetectEncoder(path));
public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default)
=> source.SaveAsync(path, source.DetectEncoder(path), cancellationToken);
/// <summary>
/// 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="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="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The encoder is null.</exception>
/// <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(encoder, nameof(encoder));
using (Stream fs = source.GetConfiguration().FileSystem.Create(path))
{
await source.SaveAsync(fs, encoder).ConfigureAwait(false);
}
using Stream fs = source.GetConfiguration().FileSystem.Create(path);
await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false);
}
/// <summary>

15
src/ImageSharp/ImageSharp.csproj

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

5
src/ImageSharp/Image{TPixel}.cs

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

12
src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs

@ -201,7 +201,17 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void FromRgba32(Rgba32 source) => this = source.Rgb;
public void FromRgba32(Rgba32 source)
{
#if NETSTANDARD2_0
// See https://github.com/SixLabors/ImageSharp/issues/1275
this.R = source.R;
this.G = source.G;
this.B = source.B;
#else
this = source.Rgb;
#endif
}
/// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)]

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.
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
namespace SixLabors.ImageSharp.Processing
@ -12,144 +11,230 @@ namespace SixLabors.ImageSharp.Processing
public static class DetectEdgesExtensions
{
/// <summary>
/// Detects any edges within the image. Uses the <see cref="SobelProcessor"/> filter
/// operating in grayscale mode.
/// Detects any edges within the image.
/// Uses the <see cref="KnownEdgeDetectorKernels.Sobel"/> kernel operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) =>
DetectEdges(source, new SobelProcessor(true));
DetectEdges(source, KnownEdgeDetectorKernels.Sobel);
/// <summary>
/// Detects any edges within the image. Uses the <see cref="SobelProcessor"/> filter
/// operating in grayscale mode.
/// Detects any edges within the image.
/// Uses the <see cref="KnownEdgeDetectorKernels.Sobel"/> kernel operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</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, Rectangle rectangle) =>
DetectEdges(source, rectangle, new SobelProcessor(true));
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
Rectangle rectangle) =>
DetectEdges(source, KnownEdgeDetectorKernels.Sobel, rectangle);
/// <summary>
/// Detects any edges within the image.
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <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>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectionOperators filter) =>
DetectEdges(source, GetProcessor(filter, true));
EdgeDetector2DKernel kernel) =>
DetectEdges(source, kernel, true);
/// <summary>
/// Detects any edges within the image.
/// Detects any edges within the image using a <see cref="EdgeDetector2DKernel"/>.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="filter">The filter for detecting edges.</param>
/// <param name="grayscale">Whether to convert the image to grayscale first. Defaults to true.</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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectionOperators filter,
bool grayscale) =>
DetectEdges(source, GetProcessor(filter, grayscale));
EdgeDetector2DKernel kernel,
bool grayscale)
{
var processor = new EdgeDetector2DProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
/// <summary>
/// Detects any edges within the image.
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <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">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </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>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectionOperators filter,
Rectangle rectangle,
bool grayscale = true) =>
DetectEdges(source, rectangle, GetProcessor(filter, grayscale));
EdgeDetector2DKernel kernel,
Rectangle rectangle) =>
DetectEdges(source, kernel, true, rectangle);
/// <summary>
/// Detects any edges within the image.
/// Detects any edges within the image using a <see cref="EdgeDetector2DKernel"/>.
/// </summary>
/// <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>
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>
/// Detects any edges within the image.
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// <param name="kernel">The edge detector kernel.</param>
/// <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 name="filter">The filter for detecting edges.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
private static IImageProcessingContext DetectEdges(
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
Rectangle rectangle,
IImageProcessor filter)
EdgeDetectorKernel kernel,
bool grayscale)
{
source.ApplyProcessor(filter, rectangle);
var processor = new EdgeDetectorProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
private static IImageProcessor GetProcessor(EdgeDetectionOperators filter, bool grayscale)
{
IImageProcessor processor;
switch (filter)
{
case EdgeDetectionOperators.Kayyali:
processor = new KayyaliProcessor(grayscale);
break;
case EdgeDetectionOperators.Kirsch:
processor = new KirschProcessor(grayscale);
break;
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;
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="kernel">The 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,
EdgeDetectorKernel kernel,
Rectangle rectangle) =>
DetectEdges(source, kernel, true, rectangle);
case EdgeDetectionOperators.RobertsCross:
processor = new RobertsCrossProcessor(grayscale);
break;
/// <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 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:
processor = new RobinsonProcessor(grayscale);
break;
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </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:
processor = new ScharrProcessor(grayscale);
break;
/// <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>
/// <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:
processor = new SobelProcessor(grayscale);
break;
}
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </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>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly DenseMatrix<float> kernelX;
private readonly DenseMatrix<float> kernelY;
private readonly bool grayscale;
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetector2DProcessor{TPixel}"/> class.
/// </summary>
/// <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="kernelY">The vertical gradient operator.</param>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
/// <param name="definition">The <see cref="EdgeDetector2DProcessor"/> defining the processor parameters.</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>
internal EdgeDetector2DProcessor(
public EdgeDetector2DProcessor(
Configuration configuration,
in DenseMatrix<float> kernelX,
in DenseMatrix<float> kernelY,
bool grayscale,
EdgeDetector2DProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same.");
this.KernelX = kernelX;
this.KernelY = kernelY;
this.Grayscale = grayscale;
this.kernelX = definition.Kernel.KernelX;
this.kernelY = definition.Kernel.KernelY;
this.grayscale = definition.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/>
protected override void BeforeImageApply()
{
@ -57,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
opaque.Execute();
}
if (this.Grayscale)
if (this.grayscale)
{
new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle);
}
@ -68,7 +55,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc />
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);
}

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>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly DenseMatrix<float>[] kernels;
private readonly bool grayscale;
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorCompassProcessor{TPixel}"/> class.
/// </summary>
/// <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="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
/// <param name="definition">The <see cref="EdgeDetectorCompassProcessor"/> defining the processor parameters.</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>
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)
{
this.Grayscale = grayscale;
this.Kernels = kernels;
this.grayscale = definition.Grayscale;
this.kernels = definition.Kernel.Flatten();
}
private CompassKernels Kernels { get; }
private bool Grayscale { get; }
/// <inheritdoc/>
protected override void BeforeImageApply()
{
@ -45,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
opaque.Execute();
}
if (this.Grayscale)
if (this.grayscale)
{
new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle);
}
@ -56,29 +58,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
DenseMatrix<float>[] kernels = this.Kernels.Flatten();
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
// We need a clean copy for each pass to start from
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);
}
if (kernels.Length == 1)
if (this.kernels.Length == 1)
{
return;
}
// 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 (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);
}

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

@ -6,26 +6,37 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <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>
public abstract class EdgeDetectorProcessor : IImageProcessor
public sealed class EdgeDetectorProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorProcessor"/> class.
/// </summary>
/// <param name="grayscale">A value indicating whether to convert the image to grayscale before performing edge detection.</param>
protected EdgeDetectorProcessor(bool grayscale)
/// <param name="kernel">The edge detector kernel.</param>
/// <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;
}
/// <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>
public bool Grayscale { get; }
/// <inheritdoc />
public abstract IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>;
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
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>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool grayscale;
private readonly DenseMatrix<float> kernelXY;
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorProcessor{TPixel}"/> class.
/// </summary>
/// <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="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
/// <param name="definition">The <see cref="EdgeDetectorProcessor"/> defining the processor parameters.</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>
public EdgeDetectorProcessor(
Configuration configuration,
in DenseMatrix<float> kernelXY,
bool grayscale,
EdgeDetectorProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.KernelXY = kernelXY;
this.Grayscale = grayscale;
this.kernelXY = definition.Kernel.KernelXY;
this.grayscale = definition.Grayscale;
}
public bool Grayscale { get; }
/// <summary>
/// Gets the 2d gradient operator.
/// </summary>
public DenseMatrix<float> KernelXY { get; }
/// <inheritdoc/>
protected override void BeforeImageApply()
{
@ -48,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
opaque.Execute();
}
if (this.Grayscale)
if (this.grayscale)
{
new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle);
}
@ -59,10 +53,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, this.KernelXY, true, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
using var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle);
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.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <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>
internal class KirschKernels : CompassKernels
internal static class KirschKernels
{
/// <summary>
/// Gets the North gradient operator
/// </summary>
public override DenseMatrix<float> North =>
public static DenseMatrix<float> North =>
new float[,]
{
{ 5, 5, 5 },
@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the NorthWest gradient operator
/// </summary>
public override DenseMatrix<float> NorthWest =>
public static DenseMatrix<float> NorthWest =>
new float[,]
{
{ 5, 5, -3 },
@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the West gradient operator
/// </summary>
public override DenseMatrix<float> West =>
public static DenseMatrix<float> West =>
new float[,]
{
{ 5, -3, -3 },
@ -44,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the SouthWest gradient operator
/// </summary>
public override DenseMatrix<float> SouthWest =>
public static DenseMatrix<float> SouthWest =>
new float[,]
{
{ -3, -3, -3 },
@ -55,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the South gradient operator
/// </summary>
public override DenseMatrix<float> South =>
public static DenseMatrix<float> South =>
new float[,]
{
{ -3, -3, -3 },
@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the SouthEast gradient operator
/// </summary>
public override DenseMatrix<float> SouthEast =>
public static DenseMatrix<float> SouthEast =>
new float[,]
{
{ -3, -3, -3 },
@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the East gradient operator
/// </summary>
public override DenseMatrix<float> East =>
public static DenseMatrix<float> East =>
new float[,]
{
{ -3, -3, 5 },
@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the NorthEast gradient operator
/// </summary>
public override DenseMatrix<float> NorthEast =>
public static DenseMatrix<float> NorthEast =>
new float[,]
{
{ -3, 5, 5 },
@ -96,4 +97,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ -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.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <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>
internal static class LaplacianKernels
{
@ -31,4 +33,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ 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.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Contains the kernels used for Robinson edge detection.
/// <see href="http://www.tutorialspoint.com/dip/Robinson_Compass_Mask.htm"/>
/// </summary>
internal class RobinsonKernels : CompassKernels
internal static class RobinsonKernels
{
/// <summary>
/// Gets the North gradient operator
/// </summary>
public override DenseMatrix<float> North =>
public static DenseMatrix<float> North =>
new float[,]
{
{ 1, 2, 1 },
@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the NorthWest gradient operator
/// </summary>
public override DenseMatrix<float> NorthWest =>
public static DenseMatrix<float> NorthWest =>
new float[,]
{
{ 2, 1, 0 },
@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the West gradient operator
/// </summary>
public override DenseMatrix<float> West =>
public static DenseMatrix<float> West =>
new float[,]
{
{ 1, 0, -1 },
@ -44,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the SouthWest gradient operator
/// </summary>
public override DenseMatrix<float> SouthWest =>
public static DenseMatrix<float> SouthWest =>
new float[,]
{
{ 0, -1, -2 },
@ -55,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the South gradient operator
/// </summary>
public override DenseMatrix<float> South =>
public static DenseMatrix<float> South =>
new float[,]
{
{ -1, -2, -1 },
@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the SouthEast gradient operator
/// </summary>
public override DenseMatrix<float> SouthEast =>
public static DenseMatrix<float> SouthEast =>
new float[,]
{
{ -2, -1, 0 },
@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the East gradient operator
/// </summary>
public override DenseMatrix<float> East =>
public static DenseMatrix<float> East =>
new float[,]
{
{ -1, 0, 1 },
@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the NorthEast gradient operator
/// </summary>
public override DenseMatrix<float> NorthEast =>
public static DenseMatrix<float> NorthEast =>
new float[,]
{
{ 0, 1, 2 },
@ -96,4 +97,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ -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();
// 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.
new BinaryThresholdProcessor(this.definition.Threshold).Execute(this.Configuration, temp, this.SourceRectangle);

35
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.IO;
using BenchmarkDotNet.Attributes;
@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Config(typeof(Config.ShortClr))]
public class DecodeJpegParseStreamOnly
{
[Params(TestImages.Jpeg.Baseline.Jpeg420Exif)]
[Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)]
public string TestImage { get; set; }
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
@ -37,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
[Benchmark(Description = "JpegDecoderCore.ParseStream")]
public void ParseStreamPdfJs()
public void ParseStream()
{
using var memoryStream = new MemoryStream(this.jpegBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream);
@ -46,22 +45,18 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
decoder.ParseStream(bufferedStream);
decoder.Dispose();
}
// RESULTS (2019 April 23):
//
// BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5)
// Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores
// .NET Core SDK=2.2.202
// [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
// Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0
// Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
//
// | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------------------------- |----- |-------- |--------------------- |---------:|---------:|----------:|------:|--------:|---------:|------:|------:|----------:|
// | 'System.Drawing FULL' | Clr | Clr | Jpg/b(...)f.jpg [28] | 18.69 ms | 8.273 ms | 0.4535 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.89 KB |
// | JpegDecoderCore.ParseStream | Clr | Clr | Jpg/b(...)f.jpg [28] | 15.76 ms | 4.266 ms | 0.2339 ms | 0.84 | 0.03 | - | - | - | 11.83 KB |
// | | | | | | | | | | | | | |
// | 'System.Drawing FULL' | Core | Core | Jpg/b(...)f.jpg [28] | 17.68 ms | 2.711 ms | 0.1486 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB |
// | JpegDecoderCore.ParseStream | Core | Core | Jpg/b(...)f.jpg [28] | 14.27 ms | 3.671 ms | 0.2012 ms | 0.81 | 0.00 | - | - | - | 11.76 KB |
}
/*
| Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------------------- |----------- |-------------- |--------------------- |---------:|----------:|----------:|------:|--------:|------:|------:|----------:|
| 'System.Drawing FULL' | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.828 ms | 0.9885 ms | 0.0542 ms | 1.00 | 46.8750 | - | - | 211566 B |
| JpegDecoderCore.ParseStream | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.833 ms | 0.2923 ms | 0.0160 ms | 1.00 | - | - | - | 12416 B |
| | | | | | | | | | | | |
| 'System.Drawing FULL' | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 6.018 ms | 2.1374 ms | 0.1172 ms | 1.00 | 46.8750 | - | - | 210768 B |
| JpegDecoderCore.ParseStream | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 4.382 ms | 0.9009 ms | 0.0494 ms | 0.73 | - | - | - | 12360 B |
| | | | | | | | | | | | |
| 'System.Drawing FULL' | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 5.714 ms | 0.4078 ms | 0.0224 ms | 1.00 | - | - | - | 176 B |
| JpegDecoderCore.ParseStream | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 4.239 ms | 1.0943 ms | 0.0600 ms | 0.74 | - | - | - | 12406 B |
*/
}

52
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs

@ -90,45 +90,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
}
// RESULTS (2018 November 4):
//
// BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134
// Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
// Frequency=2742191 Hz, Resolution=364.6719 ns, Timer=TSC
// .NET Core SDK=2.1.403
// [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
//
// Method | TestImage | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// ------------------------------- |-------------------------------------------- |-----------:|-----------:|----------:|-------:|---------:|----------:|---------:|---------:|------------:|
// 'Decode Jpeg - System.Drawing' | Jpg/baseline/Lake.jpg | 6.117 ms | 0.3923 ms | 0.0222 ms | 1.00 | 0.00 | 62.5000 | - | - | 205.83 KB |
// 'Decode Jpeg - ImageSharp' | Jpg/baseline/Lake.jpg | 18.126 ms | 0.6023 ms | 0.0340 ms | 2.96 | 0.01 | - | - | - | 19.97 KB |
// | | | | | | | | | | |
// 'Decode Jpeg - System.Drawing' | Jpg/baseline/jpeg420exif.jpg | 17.063 ms | 2.6096 ms | 0.1474 ms | 1.00 | 0.00 | 218.7500 | - | - | 757.04 KB |
// 'Decode Jpeg - ImageSharp' | Jpg/baseline/jpeg420exif.jpg | 41.366 ms | 1.0115 ms | 0.0572 ms | 2.42 | 0.02 | - | - | - | 21.94 KB |
// | | | | | | | | | | |
// 'Decode Jpeg - System.Drawing' | Jpg/issues/Issue518-Bad-RST-Progressive.jpg | 428.282 ms | 94.9163 ms | 5.3629 ms | 1.00 | 0.00 | 2375.0000 | - | - | 7403.76 KB |
// 'Decode Jpeg - ImageSharp' | Jpg/issues/Issue518-Bad-RST-Progressive.jpg | 386.698 ms | 33.0065 ms | 1.8649 ms | 0.90 | 0.01 | 125.0000 | 125.0000 | 125.0000 | 35186.97 KB |
// | | | | | | | | | | |
// 'Decode Jpeg - System.Drawing' | Jpg/issues/issue750-exif-tranform.jpg | 95.192 ms | 3.1762 ms | 0.1795 ms | 1.00 | 0.00 | 1750.0000 | - | - | 5492.63 KB |
// 'Decode Jpeg - ImageSharp' | Jpg/issues/issue750-exif-tranform.jpg | 230.158 ms | 48.8128 ms | 2.7580 ms | 2.42 | 0.02 | 312.5000 | 312.5000 | 312.5000 | 58834.66 KB |
// RESULTS (2019 April 23):
//
// BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5)
// Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores
// .NET Core SDK=2.2.202
// [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
// Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
//
// | Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |------------------------------- |--------------------- |-----------:|-----------:|-----------:|------:|--------:|----------:|------:|------:|------------:|
// | 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 6.957 ms | 9.618 ms | 0.5272 ms | 1.00 | 0.00 | 93.7500 | - | - | 205.83 KB |
// | 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 18.348 ms | 8.876 ms | 0.4865 ms | 2.65 | 0.23 | - | - | - | 14.49 KB |
// | | | | | | | | | | | |
// | 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 18.687 ms | 11.632 ms | 0.6376 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB |
// | 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 41.990 ms | 25.514 ms | 1.3985 ms | 2.25 | 0.10 | - | - | - | 15.48 KB |
// | | | | | | | | | | | |
// | 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 477.265 ms | 732.126 ms | 40.1303 ms | 1.00 | 0.00 | 3000.0000 | - | - | 7403.76 KB |
// | 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 348.545 ms | 91.480 ms | 5.0143 ms | 0.73 | 0.06 | - | - | - | 35177.21 KB |
/*
| Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------------------------------- |--------------------- |-----------:|------------:|-----------:|------:|--------:|------:|------:|------:|-----------:|
| 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 5.122 ms | 1.3978 ms | 0.0766 ms | 1.00 | 0.00 | - | - | - | 176 B |
| 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 11.991 ms | 0.2514 ms | 0.0138 ms | 2.34 | 0.03 | - | - | - | 15816 B |
| | | | | | | | | | | |
| 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 14.943 ms | 1.8410 ms | 0.1009 ms | 1.00 | 0.00 | - | - | - | 176 B |
| 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 29.759 ms | 1.5452 ms | 0.0847 ms | 1.99 | 0.01 | - | - | - | 16824 B |
| | | | | | | | | | | |
| 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 388.229 ms | 382.8946 ms | 20.9877 ms | 1.00 | 0.00 | - | - | - | 216 B |
| 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 276.490 ms | 195.5104 ms | 10.7166 ms | 0.71 | 0.01 | - | - | - | 36022368 B |
*/
}
}

18
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs

@ -10,6 +10,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using SixLabors.ImageSharp.Tests;
using CoreImage = SixLabors.ImageSharp.Image;
public class EncodeJpeg : BenchmarkBase
@ -24,7 +25,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
if (this.bmpStream == null)
{
this.bmpStream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp");
const string TestImage = TestImages.Bmp.Car;
this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = CoreImage.Load<Rgba32>(this.bmpStream);
this.bmpStream.Position = 0;
this.bmpDrawing = Image.FromStream(this.bmpStream);
@ -58,3 +60,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
}
}
/*
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.959 (1909/November2018Update/19H2)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.302
[Host] : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
DefaultJob : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
| Method | Mean | Error | StdDev | Ratio | RatioSD |
|---------------------- |---------:|----------:|----------:|------:|--------:|
| 'System.Drawing Jpeg' | 4.297 ms | 0.0244 ms | 0.0228 ms | 1.00 | 0.00 |
| 'ImageSharp Jpeg' | 5.286 ms | 0.1034 ms | 0.0967 ms | 1.23 | 0.02 |
*/

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

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

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

@ -282,7 +282,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var actualResults = default(Block8x8F);
Block8x8F.Quantize(&block, &actualResults, &qt, unzig.Data);
Block8x8F.Quantize(ref block, ref actualResults, ref qt, ref unzig);
for (int i = 0; i < Block8x8F.Size; i++)
{

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

Loading…
Cancel
Save