Browse Source

Merge branch 'main' into convolution-border-wrapping

pull/2060/head
James Jackson-South 4 years ago
committed by GitHub
parent
commit
b91cd0788a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 38
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  3. 38
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  4. 25
      src/ImageSharp/Formats/IImageDecoder.cs
  5. 11
      src/ImageSharp/Formats/IImageInfoDetector.cs
  6. 131
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  7. 43
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  8. 19
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  9. 37
      src/ImageSharp/Formats/Pbm/PbmDecoder.cs
  10. 110
      src/ImageSharp/Formats/Png/PngDecoder.cs
  11. 38
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  12. 36
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  13. 49
      src/ImageSharp/Formats/Webp/WebpDecoder.cs
  14. 56
      src/ImageSharp/IO/ChunkedMemoryStream.cs
  15. 118
      src/ImageSharp/Image.Decode.cs
  16. 10
      src/ImageSharp/Image.FromFile.cs
  17. 36
      src/ImageSharp/Image.FromStream.cs
  18. 6
      src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs
  19. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  20. 15
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  21. 27
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  22. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs
  23. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  24. 48
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
  25. 24
      tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs
  26. 2
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  27. 2
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  28. 10
      tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
  29. 15
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  30. 34
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  31. 2
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
  32. 2
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  33. 8
      tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
  34. 80
      tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs
  35. 11
      tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
  36. 4
      tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs
  37. 4
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs
  38. 6
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  39. 23
      tests/ImageSharp.Tests/TestFormat.cs
  40. 1
      tests/ImageSharp.Tests/TestImages.cs
  41. 12
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  42. 18
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs
  43. 41
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
  44. 4
      tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png
  45. 4
      tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png
  46. 3
      tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg

3
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -287,8 +287,7 @@ namespace SixLabors.ImageSharp.Advanced
where TPixel : unmanaged, IPixel<TPixel>
where TDecoder : class, IImageDecoder
{
default(TDecoder).Decode<TPixel>(default, default);
default(TDecoder).DecodeAsync<TPixel>(default, default, default);
default(TDecoder).Decode<TPixel>(default, default, default);
}
/// <summary>

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

@ -3,9 +3,6 @@
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.Bmp
@ -31,48 +28,25 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
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));
return new BmpDecoderCore(configuration, this).Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
return new BmpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken);
}
}
}

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

@ -3,8 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -26,48 +24,24 @@ namespace SixLabors.ImageSharp.Formats.Gif
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
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));
var decoder = new GifDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Identify(bufferedStream, default);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
return decoder.Identify(configuration, stream, cancellationToken);
}
}
}

25
src/ImageSharp/Formats/IImageDecoder.cs

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
@ -13,26 +12,6 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
public interface IImageDecoder
{
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
/// <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>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/>.
/// </summary>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image Decode(Configuration configuration, Stream stream);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
@ -42,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats
/// <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, CancellationToken cancellationToken)
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
@ -53,6 +32,6 @@ namespace SixLabors.ImageSharp.Formats
/// <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, CancellationToken cancellationToken);
Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken);
}
}

11
src/ImageSharp/Formats/IImageInfoDetector.cs

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SixLabors.ImageSharp.Formats
{
@ -12,14 +11,6 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
public interface IImageInfoDetector
{
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="PixelTypeInfo"/> object</returns>
IImageInfo Identify(Configuration configuration, Stream stream);
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
@ -27,6 +18,6 @@ namespace SixLabors.ImageSharp.Formats
/// <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, CancellationToken cancellationToken);
IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken);
}
}

131
src/ImageSharp/Formats/ImageDecoderUtilities.cs

@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -13,153 +12,45 @@ namespace SixLabors.ImageSharp.Formats
{
internal static class ImageDecoderUtilities
{
/// <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="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,
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(
public static IImageInfo Identify(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> tooLargeImageExceptionFactory,
CancellationToken cancellationToken)
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
IImageInfo imageInfo = decoder.Identify(bufferedReadStream, cancellationToken);
return Task.FromResult(imageInfo);
return decoder.Identify(bufferedReadStream, cancellationToken);
}
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);
throw new InvalidImageContentException(decoder.Dimensions, 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="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>(
public static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
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);
=> decoder.Decode<TPixel>(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken);
public static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory)
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
return decoder.Decode<TPixel>(bufferedReadStream, default);
return decoder.Decode<TPixel>(bufferedReadStream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
@ -170,6 +61,6 @@ namespace SixLabors.ImageSharp.Formats
private static InvalidImageContentException DefaultLargeImageExceptionFactory(
InvalidMemoryOperationException memoryOperationException,
Size dimensions) =>
new InvalidImageContentException(dimensions, memoryOperationException);
new(dimensions, memoryOperationException);
}
}

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

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@ -17,56 +16,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgb24>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgb24>(configuration, stream, cancellationToken);
/// <inheritdoc/>
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);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgb24>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
return decoder.Identify(configuration, stream);
}
/// <inheritdoc/>
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
// 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);
}
return decoder.Identify(configuration, stream, cancellationToken);
}
}
}

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

@ -677,7 +677,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Processes the App1 marker retrieving any stored metadata
/// Processes the App1 marker retrieving any stored metadata.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
@ -687,7 +687,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
const int XmpMarkerLength = 29;
if (remaining < ExifMarkerLength || this.IgnoreMetadata)
{
// Skip the application header length
// Skip the application header length.
stream.Skip(remaining);
return;
}
@ -697,12 +697,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length.");
}
// XMP marker is the longest, so read at least that many bytes into temp.
// XMP marker is the longer then the EXIF marker, so first try read the EXIF marker bytes.
stream.Read(this.temp, 0, ExifMarkerLength);
remaining -= ExifMarkerLength;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker))
{
remaining -= ExifMarkerLength;
this.hasExif = true;
byte[] profile = new byte[remaining];
stream.Read(profile, 0, remaining);
@ -713,7 +713,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
else
{
// If the EXIF information exceeds 64K, it will be split over multiple APP1 markers
// If the EXIF information exceeds 64K, it will be split over multiple APP1 markers.
this.ExtendProfile(ref this.exifData, profile);
}
@ -722,9 +722,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength)))
{
stream.Read(this.temp, 0, XmpMarkerLength - ExifMarkerLength);
remaining -= XmpMarkerLength;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(ExifMarkerLength)))
int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength;
stream.Read(this.temp, ExifMarkerLength, remainingXmpMarkerBytes);
remaining -= remainingXmpMarkerBytes;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker))
{
this.hasXmp = true;
byte[] profile = new byte[remaining];
@ -736,7 +737,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
else
{
// If the XMP information exceeds 64K, it will be split over multiple APP1 markers
// If the XMP information exceeds 64K, it will be split over multiple APP1 markers.
this.ExtendProfile(ref this.xmpData, profile);
}

37
src/ImageSharp/Formats/Pbm/PbmDecoder.cs

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
@ -30,50 +29,26 @@ namespace SixLabors.ImageSharp.Formats.Pbm
public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgb24>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgb24>(configuration, stream, cancellationToken);
/// <inheritdoc/>
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 PbmDecoderCore(configuration);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
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));
var decoder = new PbmDecoderCore(configuration);
return decoder.Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
return decoder.Identify(configuration, stream, cancellationToken);
}
}
}

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

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png
@ -17,18 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Png
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
PngDecoderCore decoder = new(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
PngDecoderCore decoder = new(configuration, true);
IImageInfo info = decoder.Identify(configuration, stream);
IImageInfo info = decoder.Identify(configuration, stream, cancellationToken);
stream.Position = 0;
PngMetadata meta = info.Metadata.GetPngMetadata();
@ -40,118 +39,49 @@ namespace SixLabors.ImageSharp.Formats.Png
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<L16>(configuration, stream)
: this.Decode<La32>(configuration, stream);
? this.Decode<L16>(configuration, stream, cancellationToken)
: this.Decode<La32>(configuration, stream, cancellationToken);
}
return !meta.HasTransparency
? this.Decode<L8>(configuration, stream)
: this.Decode<La16>(configuration, stream);
? this.Decode<L8>(configuration, stream, cancellationToken)
: this.Decode<La16>(configuration, stream, cancellationToken);
case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<Rgb48>(configuration, stream)
: this.Decode<Rgba64>(configuration, stream);
? this.Decode<Rgb48>(configuration, stream, cancellationToken)
: this.Decode<Rgba64>(configuration, stream, cancellationToken);
}
return !meta.HasTransparency
? this.Decode<Rgb24>(configuration, stream)
: this.Decode<Rgba32>(configuration, stream);
? this.Decode<Rgb24>(configuration, stream, cancellationToken)
: this.Decode<Rgba32>(configuration, stream, cancellationToken);
case PngColorType.Palette:
return this.Decode<Rgba32>(configuration, stream);
return this.Decode<Rgba32>(configuration, stream, cancellationToken);
case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<La32>(configuration, stream)
: this.Decode<La16>(configuration, stream);
? this.Decode<La32>(configuration, stream, cancellationToken)
: this.Decode<La16>(configuration, stream, cancellationToken);
case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<Rgba64>(configuration, stream)
: this.Decode<Rgba32>(configuration, stream);
? this.Decode<Rgba64>(configuration, stream, cancellationToken)
: this.Decode<Rgba32>(configuration, stream, cancellationToken);
default:
return this.Decode<Rgba32>(configuration, stream);
return this.Decode<Rgba32>(configuration, stream, cancellationToken);
}
}
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
PngDecoderCore decoder = new(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
PngDecoderCore decoder = new(configuration, true);
IImageInfo info = await decoder.IdentifyAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
stream.Position = 0;
PngMetadata meta = info.Metadata.GetPngMetadata();
PngColorType color = meta.ColorType.GetValueOrDefault();
PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
switch (color)
{
case PngColorType.Grayscale:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? await this.DecodeAsync<L16>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La32>(configuration, stream, cancellationToken).ConfigureAwait(false);
}
return !meta.HasTransparency
? await this.DecodeAsync<L8>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La16>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? await this.DecodeAsync<Rgb48>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba64>(configuration, stream, cancellationToken).ConfigureAwait(false);
}
return !meta.HasTransparency
? await this.DecodeAsync<Rgb24>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.Palette:
return await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16)
? await this.DecodeAsync<La32>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La16>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16)
? await this.DecodeAsync<Rgba64>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
default:
return await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
}
}
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
PngDecoderCore decoder = new(configuration, this);
return decoder.Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
PngDecoderCore decoder = new(configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
return decoder.Identify(configuration, stream, cancellationToken);
}
}
}

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

@ -3,9 +3,6 @@
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.Tga
@ -16,48 +13,25 @@ namespace SixLabors.ImageSharp.Formats.Tga
public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
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);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
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));
return new TgaDecoderCore(configuration, this).Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
return new TgaDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
return new TgaDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken);
}
}
}

36
src/ImageSharp/Formats/Tiff/TiffDecoder.cs

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -25,49 +24,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public FrameDecodingMode DecodingMode { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, "stream");
var decoder = new TiffDecoderCore(configuration, this);
return decoder.Decode<TPixel>(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, CancellationToken cancellationToken)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc/>
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));
var decoder = new TiffDecoderCore(configuration, this);
return decoder.Identify(configuration, stream);
}
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
return decoder.Identify(configuration, stream, cancellationToken);
}
}
}

49
src/ImageSharp/Formats/Webp/WebpDecoder.cs

@ -3,8 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -21,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
@ -30,39 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
try
{
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}.", ex);
}
}
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
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, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new WebpDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.DecodeAsync<TPixel>(configuration, bufferedStream, cancellationToken);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
@ -73,16 +39,15 @@ namespace SixLabors.ImageSharp.Formats.Webp
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc />
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream);
return new WebpDecoderCore(configuration, this).IdentifyAsync(configuration, bufferedStream, cancellationToken);
return new WebpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken);
}
}
}

56
src/ImageSharp/IO/ChunkedMemoryStream.cs

@ -16,19 +16,17 @@ namespace SixLabors.ImageSharp.IO
/// </summary>
internal sealed class ChunkedMemoryStream : Stream
{
/// <summary>
/// The default length in bytes of each buffer chunk.
/// </summary>
public const int DefaultBufferLength = 128 * 1024;
// The memory allocator.
private readonly MemoryAllocator allocator;
// Data
private MemoryChunk memoryChunk;
// The length of each buffer chunk
private readonly int chunkLength;
// The total number of allocated chunks
private int chunkCount;
// The length of the largest contiguous buffer that can be handled by the allocator.
private readonly int allocatorCapacity;
// Has the stream been disposed.
private bool isDisposed;
@ -49,21 +47,10 @@ namespace SixLabors.ImageSharp.IO
/// Initializes a new instance of the <see cref="ChunkedMemoryStream"/> class.
/// </summary>
public ChunkedMemoryStream(MemoryAllocator allocator)
: this(DefaultBufferLength, allocator)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChunkedMemoryStream"/> class.
/// </summary>
/// <param name="bufferLength">The length, in bytes of each buffer chunk.</param>
/// <param name="allocator">The memory allocator.</param>
public ChunkedMemoryStream(int bufferLength, MemoryAllocator allocator)
{
Guard.MustBeGreaterThan(bufferLength, 0, nameof(bufferLength));
Guard.NotNull(allocator, nameof(allocator));
this.chunkLength = bufferLength;
this.allocatorCapacity = allocator.GetBufferCapacityInBytes();
this.allocator = allocator;
}
@ -191,6 +178,9 @@ namespace SixLabors.ImageSharp.IO
case SeekOrigin.End:
this.Position = this.Length + offset;
break;
default:
ThrowInvalidSeek();
break;
}
return this.Position;
@ -219,6 +209,7 @@ namespace SixLabors.ImageSharp.IO
this.memoryChunk = null;
this.writeChunk = null;
this.readChunk = null;
this.chunkCount = 0;
}
finally
{
@ -519,17 +510,22 @@ namespace SixLabors.ImageSharp.IO
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowDisposed()
=> throw new ObjectDisposedException(null, "The stream is closed.");
private static void ThrowDisposed() => throw new ObjectDisposedException(null, "The stream is closed.");
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRange(string value)
=> throw new ArgumentOutOfRangeException(value);
private static void ThrowArgumentOutOfRange(string value) => throw new ArgumentOutOfRangeException(value);
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidSeek() => throw new ArgumentException("Invalid seek origin.");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private MemoryChunk AllocateMemoryChunk()
{
IMemoryOwner<byte> buffer = this.allocator.Allocate<byte>(this.chunkLength);
// Tweak our buffer sizes to take the minimum of the provided buffer sizes
// or the allocator buffer capacity which provides us with the largest
// available contiguous buffer size.
IMemoryOwner<byte> buffer = this.allocator.Allocate<byte>(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++)));
return new MemoryChunk
{
Buffer = buffer,
@ -547,6 +543,18 @@ namespace SixLabors.ImageSharp.IO
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetChunkSize(int i)
{
// Increment chunks sizes with moderate speed, but without using too many buffers from the same ArrayPool bucket of the default MemoryAllocator.
// https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720
#pragma warning disable IDE1006 // Naming Styles
const int _128K = 1 << 17;
const int _4M = 1 << 22;
return i < 16 ? _128K * (1 << (i / 4)) : _4M;
#pragma warning restore IDE1006 // Naming Styles
}
private sealed class MemoryChunk : IDisposable
{
private bool isDisposed;

118
src/ImageSharp/Image.Decode.cs

@ -2,11 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@ -25,7 +22,7 @@ namespace SixLabors.ImageSharp
/// The image might be filled with memory garbage.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="ImageSharp.Configuration"/></param>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="width">The width of the image</param>
/// <param name="height">The height of the image</param>
/// <param name="metadata">The <see cref="ImageMetadata"/></param>
@ -98,20 +95,6 @@ namespace SixLabors.ImageSharp
return format;
}
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param>
/// <returns>The mime type or null if none found.</returns>
private static Task<IImageFormat> InternalDetectFormatAsync(Stream stream, Configuration config)
{
// We are going to cheat here because we know that by this point we have been wrapped in a
// seekable stream then we are free to use sync APIs this is potentially brittle and may
// need a better fix in the future.
return Task.FromResult(InternalDetectFormat(stream, config));
}
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
@ -128,33 +111,17 @@ namespace SixLabors.ImageSharp
: null;
}
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param>
/// <returns>The decoder and the image format or null if none found.</returns>
private static async Task<(IImageDecoder Decoder, IImageFormat Format)> DiscoverDecoderAsync(Stream stream, Configuration config)
{
IImageFormat format = await InternalDetectFormatAsync(stream, config).ConfigureAwait(false);
IImageDecoder decoder = format != null
? config.ImageFormatsManager.FindDecoder(format)
: null;
return (decoder, format);
}
/// <summary>
/// Decodes the image stream to the current image.
/// </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 new <see cref="Image{TPixel}"/>.
/// </returns>
private static (Image<TPixel> Image, IImageFormat Format) Decode<TPixel>(Stream stream, Configuration config)
private static (Image<TPixel> Image, IImageFormat Format) Decode<TPixel>(Stream stream, Configuration config, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
@ -163,37 +130,11 @@ namespace SixLabors.ImageSharp
return (null, null);
}
Image<TPixel> img = decoder.Decode<TPixel>(config, stream);
return (img, format);
}
/// <summary>
/// Decodes the image stream to the current image.
/// </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,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
(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, cancellationToken)
.ConfigureAwait(false);
Image<TPixel> img = decoder.Decode<TPixel>(config, stream, cancellationToken);
return (img, format);
}
private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config)
private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config, CancellationToken cancellationToken = default)
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
if (decoder is null)
@ -201,19 +142,7 @@ namespace SixLabors.ImageSharp
return (null, null);
}
Image img = decoder.Decode(config, stream);
return (img, format);
}
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)
{
return (null, null);
}
Image img = await decoder.DecodeAsync(config, stream, cancellationToken).ConfigureAwait(false);
Image img = decoder.Decode(config, stream, cancellationToken);
return (img, format);
}
@ -222,47 +151,20 @@ 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>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stream stream, Configuration config)
private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stream stream, Configuration config, CancellationToken cancellationToken = default)
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
if (!(decoder is IImageInfoDetector detector))
{
return (null, null);
}
IImageInfo info = detector?.Identify(config, stream);
return (info, format);
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </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, CancellationToken cancellationToken)
{
(IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false);
if (!(decoder is IImageInfoDetector detector))
if (decoder is not IImageInfoDetector detector)
{
return (null, null);
}
if (detector is null)
{
return (null, format);
}
IImageInfo info = await detector.IdentifyAsync(config, stream, cancellationToken).ConfigureAwait(false);
IImageInfo info = detector?.Identify(config, stream, cancellationToken);
return (info, format);
}
}

10
src/ImageSharp/Image.FromFile.cs

@ -298,7 +298,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="NotSupportedException">Image format is not supported.</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(
public static async Task<Image> LoadAsync(
Configuration configuration,
string path,
IImageDecoder decoder,
@ -308,7 +308,8 @@ namespace SixLabors.ImageSharp
Guard.NotNull(path, nameof(path));
using Stream stream = configuration.FileSystem.OpenRead(path);
return LoadAsync(configuration, stream, decoder, cancellationToken);
return await LoadAsync(configuration, stream, decoder, cancellationToken)
.ConfigureAwait(false);
}
/// <summary>
@ -326,7 +327,7 @@ namespace SixLabors.ImageSharp
/// <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>(
public static async Task<Image<TPixel>> LoadAsync<TPixel>(
Configuration configuration,
string path,
IImageDecoder decoder,
@ -337,7 +338,8 @@ namespace SixLabors.ImageSharp
Guard.NotNull(path, nameof(path));
using Stream stream = configuration.FileSystem.OpenRead(path);
return LoadAsync<TPixel>(configuration, stream, decoder, cancellationToken);
return await LoadAsync<TPixel>(configuration, stream, decoder, cancellationToken)
.ConfigureAwait(false);
}
/// <summary>

36
src/ImageSharp/Image.FromStream.cs

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
configuration,
stream,
(s, _) => InternalDetectFormatAsync(s, configuration),
(s, _) => InternalDetectFormat(s, configuration),
cancellationToken);
/// <summary>
@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct),
(s, ct) => InternalIdentity(s, configuration ?? Configuration.Default, ct),
cancellationToken);
/// <summary>
@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp
public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder)
{
Guard.NotNull(decoder, nameof(decoder));
return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s));
return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s, default));
}
/// <summary>
@ -341,7 +341,7 @@ namespace SixLabors.ImageSharp
return WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => decoder.DecodeAsync(configuration, s, ct),
(s, ct) => decoder.Decode(configuration, s, ct),
cancellationToken);
}
@ -432,9 +432,9 @@ 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>(Stream stream, CancellationToken cancellationToken = default)
public static Task<(Image<TPixel> Image, IImageFormat Format)> LoadWithFormatAsync<TPixel>(Stream stream, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> await LoadWithFormatAsync<TPixel>(Configuration.Default, stream, cancellationToken).ConfigureAwait(false);
=> LoadWithFormatAsync<TPixel>(Configuration.Default, stream, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -449,7 +449,7 @@ namespace SixLabors.ImageSharp
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(Configuration.Default, stream, s => decoder.Decode<TPixel>(Configuration.Default, s));
=> WithSeekableStream(Configuration.Default, stream, s => decoder.Decode<TPixel>(Configuration.Default, s, default));
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -468,7 +468,7 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
Configuration.Default,
stream,
(s, ct) => decoder.DecodeAsync<TPixel>(Configuration.Default, s, ct),
(s, ct) => decoder.Decode<TPixel>(Configuration.Default, s, ct),
cancellationToken);
/// <summary>
@ -486,7 +486,7 @@ namespace SixLabors.ImageSharp
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(configuration, stream, s => decoder.Decode<TPixel>(configuration, s));
=> WithSeekableStream(configuration, stream, s => decoder.Decode<TPixel>(configuration, s, default));
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -511,7 +511,7 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => decoder.DecodeAsync<TPixel>(configuration, s, ct),
(s, ct) => decoder.Decode<TPixel>(configuration, s, ct),
cancellationToken);
/// <summary>
@ -586,7 +586,7 @@ namespace SixLabors.ImageSharp
(Image Image, IImageFormat Format) data = await WithSeekableStreamAsync(
configuration,
stream,
async (s, ct) => await DecodeAsync(s, configuration, ct).ConfigureAwait(false),
(s, ct) => Decode(s, configuration, ct),
cancellationToken)
.ConfigureAwait(false);
@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp
await WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => DecodeAsync<TPixel>(s, configuration, ct),
(s, ct) => Decode<TPixel>(s, configuration, ct),
cancellationToken)
.ConfigureAwait(false);
@ -759,7 +759,7 @@ namespace SixLabors.ImageSharp
private static async Task<T> WithSeekableStreamAsync<T>(
Configuration configuration,
Stream stream,
Func<Stream, CancellationToken, Task<T>> action,
Func<Stream, CancellationToken, T> action,
CancellationToken cancellationToken)
{
Guard.NotNull(configuration, nameof(configuration));
@ -770,10 +770,6 @@ namespace SixLabors.ImageSharp
throw new NotSupportedException("Cannot read from the stream.");
}
// To make sure we don't trigger anything with aspnetcore then we just need to make sure we are
// seekable and we make the copy using CopyToAsync if the stream is seekable then we aren't using
// one of the aspnetcore wrapped streams that error on sync api calls and we can use it without
// having to further wrap
if (stream.CanSeek)
{
if (configuration.ReadOrigin == ReadOrigin.Begin)
@ -781,14 +777,16 @@ namespace SixLabors.ImageSharp
stream.Position = 0;
}
return await action(stream, cancellationToken).ConfigureAwait(false);
// NOTE: We are explicitly not executing the action against the stream here as we do in WithSeekableStream() because that
// would incur synchronous IO reads which must be avoided in this asynchronous method. Instead, we will *always* run the
// code below to copy the stream to an in-memory buffer before invoking the action.
}
using var memoryStream = new ChunkedMemoryStream(configuration.MemoryAllocator);
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
memoryStream.Position = 0;
return await action(memoryStream, cancellationToken).ConfigureAwait(false);
return action(memoryStream, cancellationToken);
}
}
}

6
src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
@ -29,9 +31,11 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height, Color color)
{
Size size = source.GetCurrentSize();
var options = new ResizeOptions
{
Size = new Size(width, height),
// Prevent downsizing.
Size = new Size(Math.Max(width, size.Width), Math.Max(height, size.Height)),
Mode = ResizeMode.BoxPad,
Sampler = KnownResamplers.NearestNeighbor,
PadColor = color

2
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs

@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Returns a <see cref="ResizeKernel"/> for an index value between 0 and DestinationSize - 1.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx];
internal ref ResizeKernel GetKernel(nint destIdx) => ref this.kernels[destIdx];
/// <summary>
/// Computes the weights to apply at each pixel when resizing.

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

@ -61,9 +61,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Rectangle destinationRectangle = this.destinationRectangle;
bool compand = this.options.Compand;
bool premultiplyAlpha = this.options.PremultiplyAlpha;
TPixel fillColor = this.options.PadColor.ToPixel<TPixel>();
bool shouldFill = (this.options.Mode == ResizeMode.BoxPad || this.options.Mode == ResizeMode.Pad)
&& this.options.PadColor != default;
TPixel fillColor = this.options.PadColor.ToPixel<TPixel>();
// Handle resize dimensions identical to the original
if (source.Width == destination.Width
@ -209,21 +209,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// To reintroduce parallel processing, we would launch multiple workers
// for different row intervals of the image.
using (var worker = new ResizeWorker<TPixel>(
using var worker = new ResizeWorker<TPixel>(
configuration,
sourceRegion,
conversionModifiers,
horizontalKernelMap,
verticalKernelMap,
destination.Width,
interest,
destinationRectangle.Location))
{
worker.Initialize();
destinationRectangle.Location);
worker.Initialize();
var workingInterval = new RowInterval(interest.Top, interest.Bottom);
worker.FillDestinationPixels(workingInterval, destination.PixelBuffer);
}
var workingInterval = new RowInterval(interest.Top, interest.Bottom);
worker.FillDestinationPixels(workingInterval, destination.PixelBuffer);
}
private readonly struct NNRowOperation : IRowOperation

27
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs

@ -39,8 +39,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ResizeKernelMap verticalKernelMap;
private readonly int destWidth;
private readonly Rectangle targetWorkingRect;
private readonly Point targetOrigin;
@ -57,7 +55,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
PixelConversionModifiers conversionModifiers,
ResizeKernelMap horizontalKernelMap,
ResizeKernelMap verticalKernelMap,
int destWidth,
Rectangle targetWorkingRect,
Point targetOrigin)
{
@ -67,7 +64,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.conversionModifiers = conversionModifiers;
this.horizontalKernelMap = horizontalKernelMap;
this.verticalKernelMap = verticalKernelMap;
this.destWidth = destWidth;
this.targetWorkingRect = targetWorkingRect;
this.targetOrigin = targetOrigin;
@ -80,19 +76,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands(
this.windowBandHeight,
destWidth,
targetWorkingRect.Width,
workingBufferLimitHintInBytes);
this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight);
this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D<Vector4>(
this.workerHeight,
destWidth,
targetWorkingRect.Width,
preferContiguosImageBuffers: true,
options: AllocationOptions.Clean);
this.tempRowBuffer = configuration.MemoryAllocator.Allocate<Vector4>(this.sourceRectangle.Width);
this.tempColumnBuffer = configuration.MemoryAllocator.Allocate<Vector4>(destWidth);
this.tempColumnBuffer = configuration.MemoryAllocator.Allocate<Vector4>(targetWorkingRect.Width);
this.currentWindow = new RowInterval(0, this.workerHeight);
}
@ -118,6 +114,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// When creating transposedFirstPassBuffer, we made sure it's contiguous:
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan();
int left = this.targetWorkingRect.Left;
int right = this.targetWorkingRect.Right;
int width = this.targetWorkingRect.Width;
for (int y = rowInterval.Min; y < rowInterval.Max; y++)
{
// Ensure offsets are normalized for cropping and padding.
@ -131,9 +130,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan);
int top = kernel.StartIndex - this.currentWindow.Min;
ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top];
for (int x = 0; x < this.destWidth; x++)
for (nint x = 0; x < (right - left); x++)
{
ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * this.workerHeight);
@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase);
}
Span<TPixel> targetRowSpan = destination.DangerousGetRowSpan(y);
Span<TPixel> targetRowSpan = destination.DangerousGetRowSpan(y).Slice(left, width);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers);
}
@ -170,6 +170,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Span<Vector4> tempRowSpan = this.tempRowBuffer.GetSpan();
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan();
int left = this.targetWorkingRect.Left;
int right = this.targetWorkingRect.Right;
int targetOriginX = this.targetOrigin.X;
for (int y = calculationInterval.Min; y < calculationInterval.Max; y++)
{
Span<TPixel> sourceRow = this.source.DangerousGetRowSpan(y);
@ -184,13 +187,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// Span<Vector4> firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min);
ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min];
for (int x = this.targetWorkingRect.Left; x < this.targetWorkingRect.Right; x++)
for (nint x = left, z = 0; x < right; x++, z++)
{
ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - this.targetOrigin.X);
ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - targetOriginX);
// optimization for:
// firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan);
Unsafe.Add(ref firstPassBaseRef, x * this.workerHeight) = kernel.Convolve(tempRowSpan);
Unsafe.Add(ref firstPassBaseRef, z * this.workerHeight) = kernel.Convolve(tempRowSpan);
}
}
}

2
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
private void GenericBechmark()
{
this.preloadedImageStream.Position = 0;
using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream);
using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream, default);
}
[GlobalSetup(Target = nameof(JpegBaselineInterleaved444))]

2
tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
using var memoryStream = new MemoryStream(this.jpegBytes);
var decoder = new JpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream);
return decoder.Identify(Configuration.Default, memoryStream, default);
}
}
}

48
tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
@ -9,6 +10,7 @@ using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using PhotoSauce.MagicScaler;
@ -124,6 +126,32 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism },
action);
public Task ForEachImageParallelAsync(Func<string, Task> action)
{
int maxDegreeOfParallelism = this.MaxDegreeOfParallelism > 0
? this.MaxDegreeOfParallelism
: Environment.ProcessorCount;
int partitionSize = (int)Math.Ceiling((double)this.Images.Length / maxDegreeOfParallelism);
List<Task> tasks = new();
for (int i = 0; i < this.Images.Length; i += partitionSize)
{
int end = Math.Min(i + partitionSize, this.Images.Length);
Task task = RunPartition(i, end);
tasks.Add(task);
}
return Task.WhenAll(tasks);
Task RunPartition(int start, int end) => Task.Run(async () =>
{
for (int i = start; i < end; i++)
{
await action(this.Images[i]);
}
});
}
private void LogImageProcessed(int width, int height)
{
this.LastProcessedImageSize = new Size(width, height);
@ -197,6 +225,26 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
image.Save(output, this.imageSharpJpegEncoder);
}
public async Task ImageSharpResizeAsync(string input)
{
using FileStream output = File.Open(this.OutputPath(input), FileMode.Create);
// Resize it to fit a 150x150 square
using var image = await ImageSharpImage.LoadAsync(input);
this.LogImageProcessed(image.Width, image.Height);
image.Mutate(i => i.Resize(new ResizeOptions
{
Size = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize),
Mode = ResizeMode.Max
}));
// Reduce the size of the file
image.Metadata.ExifProfile = null;
// Save the results
await image.SaveAsync(output, this.imageSharpJpegEncoder);
}
public void MagickResize(string input)
{
using var image = new MagickImage(input);

24
tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs

@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}");
Stopwatch timer;
if (options == null || !options.ImageSharp)
if (options == null || !(options.ImageSharp || options.AsyncImageSharp))
{
RunBenchmarkSwitcher(lrs, out timer);
}
@ -74,7 +74,16 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
{
for (int i = 0; i < options.RepeatCount; i++)
{
lrs.ImageSharpBenchmarkParallel();
if (options.AsyncImageSharp)
{
lrs.ImageSharpBenchmarkParallelAsync();
}
else
{
lrs.ImageSharpBenchmarkParallel();
}
Console.WriteLine("OK");
}
}
catch (Exception ex)
@ -221,6 +230,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
private class CommandLineOptions
{
[Option('a', "async-imagesharp", Required = false, Default = false, HelpText = "Async ImageSharp without benchmark switching")]
public bool AsyncImageSharp { get; set; }
[Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")]
public bool ImageSharp { get; set; }
@ -277,7 +289,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
}
public override string ToString() =>
$"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})";
$"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.AsyncImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})";
public MemoryAllocator CreateMemoryAllocator()
{
@ -330,11 +342,17 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
}
});
private void ImageSharpBenchmarkParallelAsync() =>
this.Benchmarks.ForEachImageParallelAsync(f => this.Benchmarks.ImageSharpResizeAsync(f))
.GetAwaiter()
.GetResult();
private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize);
private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagicScalerResize);
private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapResize);
private void SkiaBitmapDecodeToTargetSizeBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapDecodeToTargetSize);
private void NetVipsBenchmarkParallel() => this.ForEachImage(this.Benchmarks.NetVipsResize);

2
tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs

@ -557,7 +557,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new BmpDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);

2
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
{
using (var stream = new UnmanagedMemoryStream(data, length))
{
using (Image<Rgba32> image = GifDecoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = GifDecoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
Assert.Equal((200, 200), (image.Width, image.Height));
}

10
tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs

@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
input.Save(memoryStream, new GifEncoder());
memoryStream.Position = 0;
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream, default))
{
GifMetadata metadata = image.Metadata.GetGifMetadata();
Assert.Equal(2, metadata.Comments.Count);
@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);
}
@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);

15
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata;
@ -84,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
using (Image image = decoder.Decode(Configuration.Default, stream))
using (Image image = decoder.Decode(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
@ -102,7 +101,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -118,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
@ -131,7 +130,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
using (Image image = JpegDecoder.Decode(Configuration.Default, stream))
using (Image image = JpegDecoder.Decode(Configuration.Default, stream, default))
{
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
@ -153,7 +152,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream);
IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream, default);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
@ -181,8 +180,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = useIdentify
? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream)
: decoder.Decode<Rgba32>(Configuration.Default, stream);
? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default)
: decoder.Decode<Rgba32>(Configuration.Default, stream, default);
test(imageInfo);
}

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

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private ITestOutputHelper Output { get; }
private static JpegDecoder JpegDecoder => new JpegDecoder();
private static JpegDecoder JpegDecoder => new();
[Fact]
public void ParseStream_BasicPropertiesAreCorrect()
@ -139,27 +139,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.IsType<InvalidMemoryOperationException>(ex.InnerException);
}
[Theory]
[InlineData(0)]
[InlineData(0.5)]
[InlineData(0.9)]
public async Task DecodeAsync_IsCancellable(int percentageOfStreamReadToCancel)
[Fact]
public async Task DecodeAsync_IsCancellable()
{
var cts = new CancellationTokenSource();
string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small);
using var pausedStream = new PausedStream(file);
pausedStream.OnWaiting(s =>
{
if (s.Position >= s.Length * percentageOfStreamReadToCancel)
{
cts.Cancel();
pausedStream.Release();
}
else
{
// allows this/next wait to unblock
pausedStream.Next();
}
cts.Cancel();
pausedStream.Release();
});
var config = Configuration.CreateDefaultInstance();
@ -213,6 +202,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
// https://github.com/SixLabors/ImageSharp/issues/2057
[Theory]
[WithFile(TestImages.Jpeg.Issues.Issue2057App1Parsing, PixelTypes.Rgba32)]
public void Issue2057_DecodeWorks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
// DEBUG ONLY!
// The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm"
// into "\tests\Images\ActualOutput\JpegDecoderTests\"

2
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs

@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder();
ImageFormatException exception =
Assert.Throws<InvalidImageContentException>(() => decoder.Decode<Rgb24>(Configuration.Default, memStream));
Assert.Throws<InvalidImageContentException>(() => decoder.Decode<Rgb24>(Configuration.Default, memStream, default));
Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message);
}

2
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder();
Image image = decoder.Decode(Configuration.Default, stream);
Image image = decoder.Decode(Configuration.Default, stream, default);
PngMetadata metadata = image.Metadata.GetPngMetadata();
Assert.Equal(pngColorType, metadata.ColorType);

8
tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs

@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
input.Save(memoryStream, new PngEncoder());
memoryStream.Position = 0;
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream, default))
{
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
});
memoryStream.Position = 0;
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream, default))
{
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.Contains(meta.TextData, m => m.Equals(expectedText));
@ -212,7 +212,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new PngDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new PngDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);

80
tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs

@ -18,19 +18,19 @@ namespace SixLabors.ImageSharp.Tests.IO
/// </summary>
public class ChunkedMemoryStreamTests
{
private readonly MemoryAllocator allocator;
/// <summary>
/// The default length in bytes of each buffer chunk when allocating large buffers.
/// </summary>
private const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb
public ChunkedMemoryStreamTests()
{
this.allocator = Configuration.Default.MemoryAllocator;
}
/// <summary>
/// The default length in bytes of each buffer chunk when allocating small buffers.
/// </summary>
private const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb
[Fact]
public void MemoryStream_Ctor_InvalidCapacities()
{
Assert.Throws<ArgumentOutOfRangeException>(() => new ChunkedMemoryStream(int.MinValue, this.allocator));
Assert.Throws<ArgumentOutOfRangeException>(() => new ChunkedMemoryStream(0, this.allocator));
}
private readonly MemoryAllocator allocator;
public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator;
[Fact]
public void MemoryStream_GetPositionTest_Negative()
@ -61,11 +61,11 @@ namespace SixLabors.ImageSharp.Tests.IO
}
[Theory]
[InlineData(ChunkedMemoryStream.DefaultBufferLength)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)]
[InlineData(DefaultSmallChunkSize)]
[InlineData((int)(DefaultSmallChunkSize * 1.5))]
[InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(DefaultSmallChunkSize * 16)]
public void MemoryStream_ReadByteTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.IO
ms.CopyTo(cms);
cms.Position = 0;
var expected = ms.ToArray();
byte[] expected = ms.ToArray();
for (int i = 0; i < expected.Length; i++)
{
@ -82,11 +82,11 @@ namespace SixLabors.ImageSharp.Tests.IO
}
[Theory]
[InlineData(ChunkedMemoryStream.DefaultBufferLength)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)]
[InlineData(DefaultSmallChunkSize)]
[InlineData((int)(DefaultSmallChunkSize * 1.5))]
[InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(DefaultSmallChunkSize * 16)]
public void MemoryStream_ReadByteBufferTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
@ -94,8 +94,8 @@ namespace SixLabors.ImageSharp.Tests.IO
ms.CopyTo(cms);
cms.Position = 0;
var expected = ms.ToArray();
var buffer = new byte[2];
byte[] expected = ms.ToArray();
byte[] buffer = new byte[2];
for (int i = 0; i < expected.Length; i += 2)
{
cms.Read(buffer);
@ -105,11 +105,11 @@ namespace SixLabors.ImageSharp.Tests.IO
}
[Theory]
[InlineData(ChunkedMemoryStream.DefaultBufferLength)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)]
[InlineData(DefaultSmallChunkSize)]
[InlineData((int)(DefaultSmallChunkSize * 1.5))]
[InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(DefaultSmallChunkSize * 16)]
public void MemoryStream_ReadByteBufferSpanTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.IO
ms.CopyTo(cms);
cms.Position = 0;
var expected = ms.ToArray();
byte[] expected = ms.ToArray();
Span<byte> buffer = new byte[2];
for (int i = 0; i < expected.Length; i += 2)
{
@ -257,24 +257,24 @@ namespace SixLabors.ImageSharp.Tests.IO
public void MemoryStream_CopyTo_Invalid()
{
ChunkedMemoryStream memoryStream;
const string BufferSize = "bufferSize";
const string bufferSize = nameof(bufferSize);
using (memoryStream = new ChunkedMemoryStream(this.allocator))
{
const string Destination = "destination";
Assert.Throws<ArgumentNullException>(Destination, () => memoryStream.CopyTo(destination: null));
const string destination = nameof(destination);
Assert.Throws<ArgumentNullException>(destination, () => memoryStream.CopyTo(destination: null));
// Validate the destination parameter first.
Assert.Throws<ArgumentNullException>(Destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0));
Assert.Throws<ArgumentNullException>(Destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1));
Assert.Throws<ArgumentNullException>(destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0));
Assert.Throws<ArgumentNullException>(destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1));
// Then bufferSize.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
Assert.Throws<ArgumentOutOfRangeException>(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense.
Assert.Throws<ArgumentOutOfRangeException>(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
}
// After the Stream is disposed, we should fail on all CopyTos.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
Assert.Throws<ArgumentOutOfRangeException>(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated.
Assert.Throws<ArgumentOutOfRangeException>(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
ChunkedMemoryStream disposedStream = memoryStream;
@ -369,7 +369,7 @@ namespace SixLabors.ImageSharp.Tests.IO
private MemoryStream CreateTestStream(int length)
{
var buffer = new byte[length];
byte[] buffer = new byte[length];
var random = new Random();
random.NextBytes(buffer);

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

@ -63,12 +63,11 @@ namespace SixLabors.ImageSharp.Tests
this.localImageFormatMock = new Mock<IImageFormat>();
var detector = new Mock<IImageInfoDetector>();
detector.Setup(x => x.Identify(It.IsAny<Configuration>(), It.IsAny<Stream>())).Returns(this.localImageInfoMock.Object);
detector.Setup(x => x.IdentifyAsync(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())).ReturnsAsync(this.localImageInfoMock.Object);
detector.Setup(x => x.Identify(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())).Returns(this.localImageInfoMock.Object);
this.localDecoder = detector.As<IImageDecoder>();
this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>()))
.Callback<Configuration, Stream>((c, s) =>
this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<Configuration, Stream, CancellationToken>((c, s, ct) =>
{
using (var ms = new MemoryStream())
{
@ -78,8 +77,8 @@ namespace SixLabors.ImageSharp.Tests
})
.Returns(this.localStreamReturnImageRgba32);
this.localDecoder.Setup(x => x.Decode(It.IsAny<Configuration>(), It.IsAny<Stream>()))
.Callback<Configuration, Stream>((c, s) =>
this.localDecoder.Setup(x => x.Decode(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<Configuration, Stream, CancellationToken>((c, s, ct) =>
{
using (var ms = new MemoryStream())
{

4
tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, this.DataStream));
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, this.DataStream, default));
}
[Fact]
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream));
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream, default));
}
[Fact]

4
tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, stream, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, stream));
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, stream, default));
}
[Fact]
@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load(this.TopLevelConfiguration, stream, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream));
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream, default));
}
[Fact]

6
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -472,7 +472,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
var options = new ResizeOptions
{
Size = new Size(image.Width + 200, image.Height + 200),
Mode = ResizeMode.BoxPad
Mode = ResizeMode.BoxPad,
PadColor = Color.HotPink
};
image.Mutate(x => x.Resize(options));
@ -580,7 +581,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
var options = new ResizeOptions
{
Size = new Size(image.Width + 200, image.Height),
Mode = ResizeMode.Pad
Mode = ResizeMode.Pad,
PadColor = Color.Lavender
};
image.Mutate(x => x.Resize(options));

23
tests/ImageSharp.Tests/TestFormat.cs

@ -212,19 +212,16 @@ namespace SixLabors.ImageSharp.Tests
public int HeaderSize => this.testFormat.HeaderSize;
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> this.DecodeImpl<TPixel>(configuration, stream, default).GetAwaiter().GetResult();
=> this.DecodeImpl<TPixel>(configuration, stream);
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> this.DecodeImpl<TPixel>(configuration, stream, cancellationToken);
private async Task<Image<TPixel>> DecodeImpl<TPixel>(Configuration config, Stream stream, CancellationToken cancellationToken)
private Image<TPixel> DecodeImpl<TPixel>(Configuration config, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var ms = new MemoryStream();
await stream.CopyToAsync(ms, config.StreamProcessingBufferSize, cancellationToken);
stream.CopyTo(ms, config.StreamProcessingBufferSize);
byte[] marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray();
this.testFormat.DecodeCalls.Add(new DecodeOperation
{
@ -239,16 +236,10 @@ namespace SixLabors.ImageSharp.Tests
public bool IsSupportedFileFormat(Span<byte> header) => this.testFormat.IsSupportedFileFormat(header);
public Image Decode(Configuration configuration, Stream stream) => this.Decode<TestPixelForAgnosticDecode>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<TestPixelForAgnosticDecode>(configuration, stream, cancellationToken);
public IImageInfo Identify(Configuration configuration, Stream stream) =>
this.IdentifyAsync(configuration, stream, default).GetAwaiter().GetResult();
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<TestPixelForAgnosticDecode>(configuration, stream, cancellationToken);
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeImpl<Rgba32>(configuration, stream, cancellationToken);
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) =>
this.DecodeImpl<Rgba32>(configuration, stream);
}
public class TestEncoder : IImageEncoder

1
tests/ImageSharp.Tests/TestImages.cs

@ -262,6 +262,7 @@ namespace SixLabors.ImageSharp.Tests
public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg";
public const string MultipleApp01932 = "Jpg/issues/issue-1932-app0-resolution.jpg";
public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg";
public const string Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg";
public const string ExifNullArrayTag = "Jpg/issues/issue-2056-exif-null-array.jpg";
public static class Fuzz

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

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using ImageMagick.Formats;
using SixLabors.ImageSharp.Formats;
@ -59,11 +58,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
}
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(configuration, stream));
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel>
{
var bmpReadDefines = new BmpReadDefines
@ -105,9 +100,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
return new Image<TPixel>(configuration, new ImageMetadata(), framesList);
}
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<Rgba32>(configuration, stream, cancellationToken);
}
}

18
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs

@ -3,8 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -15,11 +13,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
{
public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder();
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(configuration, stream));
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using (var sourceBitmap = new System.Drawing.Bitmap(stream))
@ -49,10 +43,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
}
}
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> Task.FromResult(this.Identify(configuration, stream));
public IImageInfo Identify(Configuration configuration, Stream stream)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
using (var sourceBitmap = new System.Drawing.Bitmap(stream))
{
@ -61,9 +52,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
}
}
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<Rgba32>(configuration, stream, cancellationToken);
}
}

41
tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

@ -5,7 +5,6 @@ using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
@ -350,9 +349,6 @@ namespace SixLabors.ImageSharp.Tests
private static readonly ConcurrentDictionary<string, int> InvocationCounts =
new ConcurrentDictionary<string, int>();
private static readonly ConcurrentDictionary<string, int> InvocationCountsAsync =
new ConcurrentDictionary<string, int>();
private static readonly object Monitor = new object();
private string callerName;
@ -365,35 +361,22 @@ namespace SixLabors.ImageSharp.Tests
}
}
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCounts[this.callerName]++;
return new Image<TPixel>(42, 42);
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCountsAsync[this.callerName]++;
return Task.FromResult(new Image<TPixel>(42, 42));
}
internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName];
internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName];
internal void InitCaller(string name)
{
this.callerName = name;
InvocationCounts[name] = 0;
InvocationCountsAsync[name] = 0;
}
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<Rgba32>(configuration, stream, cancellationToken);
}
private class TestDecoderWithParameters : IImageDecoder
@ -401,9 +384,6 @@ namespace SixLabors.ImageSharp.Tests
private static readonly ConcurrentDictionary<string, int> InvocationCounts =
new ConcurrentDictionary<string, int>();
private static readonly ConcurrentDictionary<string, int> InvocationCountsAsync =
new ConcurrentDictionary<string, int>();
private static readonly object Monitor = new object();
private string callerName;
@ -420,35 +400,22 @@ namespace SixLabors.ImageSharp.Tests
}
}
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCounts[this.callerName]++;
return new Image<TPixel>(42, 42);
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCountsAsync[this.callerName]++;
return Task.FromResult(new Image<TPixel>(42, 42));
}
internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName];
internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName];
internal void InitCaller(string name)
{
this.callerName = name;
InvocationCounts[name] = 0;
InvocationCountsAsync[name] = 0;
}
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<Rgba32>(configuration, stream, cancellationToken);
}
}
}

4
tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1642520a9d4491f55af5a83280423929529e3d528637538d148b9dc2ecdd6d2d
size 365839
oid sha256:77086032bab11b91c68bc1686179063edbadc9d453574e4f087b2bbd677b4c8e
size 402367

4
tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:060826324dcd4baa1df1b075707a9bd9527cf87c1d156e3beb3d8abb499bdcd5
size 361829
oid sha256:5c0653aa2b726574fbea4cc308c269ff5e534d38bb48c0e77470c11042a395fd
size 400267

3
tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7048dee15946bf981e5b0d2481ffcb8a64684fddca07172275b13a05f01b6b63
size 1631109
Loading…
Cancel
Save